/*!
- * OOjs UI v0.12.8
+ * OOjs UI v0.12.9
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-08T20:56:08Z
+ * Date: 2015-09-22T20:09:59Z
*/
@-webkit-keyframes oo-ui-progressBarWidget-slide {
from {
}
.oo-ui-buttonElement > .oo-ui-buttonElement-button {
font-weight: bold;
+ text-decoration: none;
}
.oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
margin-left: 0;
.oo-ui-buttonElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
width: 0.9375em;
height: 0.9375em;
- margin: 0.46875em;
}
.oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
margin-left: 0.46875em;
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
color: #cccccc;
}
+.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button:focus {
+ box-shadow: none;
+}
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
opacity: 0.2;
}
+.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button,
+.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button {
+ padding-left: 2.4em;
+}
.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
margin: 0.1em 0;
- padding: 0.2em 0.8em;
+ padding: 0.5em 1em;
+ min-height: 1.2em;
+ min-width: 1em;
border-radius: 2px;
+ position: relative;
-webkit-transition: background 100ms ease, color 100ms ease, box-shadow 100ms ease;
-moz-transition: background 100ms ease, color 100ms ease, box-shadow 100ms ease;
-ms-transition: background 100ms ease, color 100ms ease, box-shadow 100ms ease;
}
.oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- line-height: 1.875em;
+ line-height: 1.2em;
+ display: inline-block;
}
.oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
- margin-left: -0.5em;
- margin-right: -0.5em;
+ position: absolute;
+ top: 0.2em;
+ left: 0.5625em;
}
-.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
- margin-right: 0.3em;
+.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ margin-left: 0.3em;
}
.oo-ui-buttonElement-framed.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
- /* -0.5 - 0.475 */
- margin-left: -0.005em;
- margin-right: -0.005em;
+ display: inline-block;
}
.oo-ui-buttonElement-framed.oo-ui-indicatorElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator,
.oo-ui-buttonElement-framed.oo-ui-indicatorElement.oo-ui-iconElement:not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
margin-left: 0.46875em;
margin-right: -0.275em;
}
+.oo-ui-buttonElement-framed.oo-ui-indicatorElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ position: relative;
+ left: 0.2em;
+}
.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
color: #ffffff;
background: #dddddd;
.oo-ui-indexLayout-stackLayout > .oo-ui-panelLayout {
padding: 1.5em;
}
+.oo-ui-indexLayout > .oo-ui-menuLayout-menu {
+ height: 2.75em;
+}
+.oo-ui-indexLayout > .oo-ui-menuLayout-content {
+ top: 2.75em;
+}
.oo-ui-fieldLayout {
display: block;
margin-bottom: 1em;
right: 18em;
bottom: 18em;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu .oo-ui-menuLayout-menu {
+.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-menu {
width: 0 !important;
height: 0 !important;
overflow: hidden;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu .oo-ui-menuLayout-content {
+.oo-ui-menuLayout.oo-ui-menuLayout-hideMenu > .oo-ui-menuLayout-content {
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top .oo-ui-menuLayout-menu {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-menu {
width: auto !important;
left: 0;
top: 0;
right: 0;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top .oo-ui-menuLayout-content {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-top > .oo-ui-menuLayout-content {
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after .oo-ui-menuLayout-menu {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-menu {
height: auto !important;
top: 0;
right: 0;
bottom: 0;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after .oo-ui-menuLayout-content {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-after > .oo-ui-menuLayout-content {
bottom: 0 !important;
left: 0 !important;
top: 0 !important;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom .oo-ui-menuLayout-menu {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-menu {
width: auto !important;
right: 0;
bottom: 0;
left: 0;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom .oo-ui-menuLayout-content {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-bottom > .oo-ui-menuLayout-content {
left: 0 !important;
top: 0 !important;
right: 0 !important;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before .oo-ui-menuLayout-menu {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-menu {
height: auto !important;
bottom: 0;
left: 0;
top: 0;
}
-.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before .oo-ui-menuLayout-content {
+.oo-ui-menuLayout.oo-ui-menuLayout-showMenu.oo-ui-menuLayout-before > .oo-ui-menuLayout-content {
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
display: inline;
}
.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link {
+ outline: 0;
cursor: default;
}
.oo-ui-barToolGroup > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link {
position: absolute;
}
.oo-ui-popupToolGroup.oo-ui-widget-disabled .oo-ui-popupToolGroup-handle {
+ outline: 0;
cursor: default;
}
.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
display: inline-block;
vertical-align: middle;
}
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
- height: 1.875em;
-}
.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
margin-top: 0;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled {
background: #dddddd;
border-color: #dddddd;
+ outline: 0;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled .oo-ui-toggleSwitchWidget-grip {
background: #ffffff;
border: 1px solid #aaaaaa;
border-radius: 0.2em;
background-color: #ffffff;
- box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2);
+ box-shadow: 0 0.15em 0 0 rgba(204, 204, 204, 0.5);
}
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-popup {
margin-top: 9px;
margin-top: -1px;
border: 1px solid #aaaaaa;
border-radius: 0 0 0.2em 0.2em;
- padding-bottom: 0.25em;
- box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2), 0 0.1em 0 0 rgba(0, 0, 0, 0.2);
+ box-shadow: 0 0.15em 0 0 rgba(204, 204, 204, 0.5);
}
.oo-ui-menuSelectWidget input {
position: absolute;
border-color: #dddddd;
background-color: #f3f3f3;
}
+.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
+ outline: 0;
+}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
opacity: 0.2;
}
right: 0;
text-overflow: ellipsis;
}
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+ float: right;
+}
.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
left: 0.25em;
}
-.oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
line-height: 2.3em;
margin: 0;
overflow: hidden;
left: 1em;
right: 1em;
}
-.oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+ color: #888888;
+}
+.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
top: 0;
width: 1.875em;
height: 1.875em;
font-weight: bold;
line-height: 1.875em;
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget .oo-ui-buttonElement-button,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget .oo-ui-buttonElement-button,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget .oo-ui-buttonElement-button {
- min-width: 1.875em;
- min-height: 1.875em;
-}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget .oo-ui-labelElement-label,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget .oo-ui-labelElement-label,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget .oo-ui-labelElement-label {
- line-height: 1.875em;
-}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-iconElement .oo-ui-iconElement-icon {
- margin-top: -0.125em;
-}
.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-framed,
.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-framed,
.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-framed {
- margin: 0.75em;
-}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-framed .oo-ui-buttonElement-button,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-framed .oo-ui-buttonElement-button,
-.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-framed .oo-ui-buttonElement-button {
- padding: 0 1em;
- vertical-align: middle;
- /* Adjust for border so text aligns with title */
- margin: -1px;
+ margin: 0.5em;
}
.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless,
.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless,
padding: 0.75em 1em;
vertical-align: middle;
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget:hover,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget:hover {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless .oo-ui-labelElement-label,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless .oo-ui-labelElement-label,
+.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement-frameless .oo-ui-labelElement-label {
+ line-height: 1.875em;
+}
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless:hover,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless:hover {
background-color: rgba(0, 0, 0, 0.05);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget:active,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget:active {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless:active,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless:active {
background-color: rgba(0, 0, 0, 0.1);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-progressive:hover,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-progressive:hover {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:hover,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:hover {
background-color: rgba(8, 126, 204, 0.05);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-progressive:active,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-progressive:active {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:active,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive:active {
background-color: rgba(8, 126, 204, 0.1);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-progressive .oo-ui-labelElement-label,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-progressive .oo-ui-labelElement-label {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive .oo-ui-labelElement-label,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive .oo-ui-labelElement-label {
font-weight: bold;
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-constructive:hover,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-constructive:hover {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-constructive:hover,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-constructive:hover {
background-color: rgba(118, 171, 54, 0.05);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-constructive:active,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-constructive:active {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-constructive:active,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-constructive:active {
background-color: rgba(118, 171, 54, 0.1);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-destructive:hover,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-destructive:hover {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:hover,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:hover {
background-color: rgba(212, 83, 83, 0.05);
}
-.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-flaggedElement-destructive:active,
-.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-flaggedElement-destructive:active {
+.oo-ui-processDialog-actions-safe .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:active,
+.oo-ui-processDialog-actions-primary .oo-ui-actionWidget.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-destructive:active {
background-color: rgba(212, 83, 83, 0.1);
}
.oo-ui-processDialog-actions-other .oo-ui-actionWidget.oo-ui-buttonElement {
/*!
- * OOjs UI v0.12.8
+ * OOjs UI v0.12.9
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-08T20:55:55Z
+ * Date: 2015-09-22T20:09:51Z
*/
( function ( OO ) {
* @return {boolean}
*/
OO.ui.isFocusableElement = function ( $element ) {
- var node = $element[ 0 ],
- nodeName = node.nodeName.toLowerCase(),
- // Check if the element have tabindex set
- isInElementGroup = /^(input|select|textarea|button|object)$/.test( nodeName ),
- // Check if the element is a link with href or if it has tabindex
- isOtherElement = (
- ( nodeName === 'a' && node.href ) ||
- !isNaN( $element.attr( 'tabindex' ) )
- ),
- // Check if the element is visible
- isVisible = (
- // This is quicker than calling $element.is( ':visible' )
- $.expr.filters.visible( node ) &&
- // Check that all parents are visible
- !$element.parents().addBack().filter( function () {
- return $.css( this, 'visibility' ) === 'hidden';
- } ).length
- ),
- isTabOk = isNaN( $element.attr( 'tabindex' ) ) || +$element.attr( 'tabindex' ) >= 0;
+ var nodeName,
+ element = $element[ 0 ];
- return (
- ( isInElementGroup ? !node.disabled : isOtherElement ) &&
- isVisible && isTabOk
- );
+ // Anything disabled is not focusable
+ if ( element.disabled ) {
+ return false;
+ }
+
+ // Check if the element is visible
+ if ( !(
+ // This is quicker than calling $element.is( ':visible' )
+ $.expr.filters.visible( element ) &&
+ // Check that all parents are visible
+ !$element.parents().addBack().filter( function () {
+ return $.css( this, 'visibility' ) === 'hidden';
+ } ).length
+ ) ) {
+ return false;
+ }
+
+ // Check if the element is ContentEditable, which is the string 'true'
+ if ( element.contentEditable === 'true' ) {
+ return true;
+ }
+
+ // Anything with a non-negative numeric tabIndex is focusable.
+ // Use .prop to avoid browser bugs
+ if ( $element.prop( 'tabIndex' ) >= 0 ) {
+ return true;
+ }
+
+ // Some element types are naturally focusable
+ // (indexOf is much faster than regex in Chrome and about the
+ // same in FF: https://jsperf.com/regex-vs-indexof-array2)
+ nodeName = element.nodeName.toLowerCase();
+ if ( [ 'input', 'select', 'textarea', 'button', 'object' ].indexOf( nodeName ) !== -1 ) {
+ return true;
+ }
+
+ // Links and areas are focusable if they have an href
+ if ( ( nodeName === 'a' || nodeName === 'area' ) && $element.attr( 'href' ) !== undefined ) {
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Find a focusable child
+ *
+ * @param {jQuery} $container Container to search in
+ * @param {boolean} [backwards] Search backwards
+ * @return {jQuery} Focusable child, an empty jQuery object if none found
+ */
+OO.ui.findFocusable = function ( $container, backwards ) {
+ var $focusable = $( [] ),
+ // $focusableCandidates is a superset of things that
+ // could get matched by isFocusableElement
+ $focusableCandidates = $container
+ .find( 'input, select, textarea, button, object, a, area, [contenteditable], [tabindex]' );
+
+ if ( backwards ) {
+ $focusableCandidates = Array.prototype.reverse.call( $focusableCandidates );
+ }
+
+ $focusableCandidates.each( function () {
+ var $this = $( this );
+ if ( OO.ui.isFocusableElement( $this ) ) {
+ $focusable = $this;
+ return false;
+ }
+ } );
+ return $focusable;
};
/**
// Label for the file selection widget when no file is currently selected
'ooui-selectfile-placeholder': 'No file is selected',
// Label for the file selection widget's drop target
- 'ooui-selectfile-dragdrop-placeholder': 'Drop file here',
- // Semicolon separator
- 'ooui-semicolon-separator': '; '
+ 'ooui-selectfile-dragdrop-placeholder': 'Drop file here'
};
/**
/**
* @event disable
*
- * A 'disable' event is emitted when a widget is disabled.
+ * A 'disable' event is emitted when the disabled state of the widget changes
+ * (i.e. on disable **and** enable).
*
* @param {boolean} disabled Widget is disabled
*/
this.$overlay = $( '<div>' );
this.$content = $( '<div>' );
+ this.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );
+ this.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );
+ this.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );
+
// Initialization
this.$overlay.addClass( 'oo-ui-window-overlay' );
this.$content
.attr( 'tabindex', 0 );
this.$frame
.addClass( 'oo-ui-window-frame' )
- .append( this.$content );
+ .append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );
this.$element
.addClass( 'oo-ui-window' )
return this;
};
+/**
+ * Called when someone tries to focus the hidden element at the end of the dialog.
+ * Sends focus back to the start of the dialog.
+ *
+ * @param {jQuery.Event} event Focus event
+ */
+OO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {
+ if ( this.$focusTrapBefore.is( event.target ) ) {
+ OO.ui.findFocusable( this.$content, true ).focus();
+ } else {
+ // this.$content is the part of the focus cycle, and is the first focusable element
+ this.$content.focus();
+ }
+};
+
/**
* Open the window.
*
this.toggle( true );
+ this.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );
+ this.$focusTraps.on( 'focus', this.focusTrapHandler );
+
this.getSetupProcess( data ).execute().done( function () {
// Force redraw by asking the browser to measure the elements' widths
win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
// Force redraw by asking the browser to measure the elements' widths
win.$element.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
win.$content.removeClass( 'oo-ui-window-content-setup' ).width();
+ win.$focusTraps.off( 'focus', win.focusTrapHandler );
win.toggle( false );
} );
};
return this;
};
+/**
+ * Element that will stick under a specified container, even when it is inserted elsewhere in the
+ * document (for example, in a OO.ui.Window's $overlay).
+ *
+ * The elements's position is automatically calculated and maintained when window is resized or the
+ * page is scrolled. If you reposition the container manually, you have to call #position to make
+ * sure the element is still placed correctly.
+ *
+ * As positioning is only possible when both the element and the container are attached to the DOM
+ * and visible, it's only done after you call #togglePositioning. You might want to do this inside
+ * the #toggle method to display a floating popup, for example.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$floatable] Node to position, assigned to #$floatable, omit to use #$element
+ * @cfg {jQuery} [$floatableContainer] Node to position below
+ */
+OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$floatable = null;
+ this.$floatableContainer = null;
+ this.$floatableWindow = null;
+ this.$floatableClosestScrollable = null;
+ this.onFloatableScrollHandler = this.position.bind( this );
+ this.onFloatableWindowResizeHandler = this.position.bind( this );
+
+ // Initialization
+ this.setFloatableContainer( config.$floatableContainer );
+ this.setFloatableElement( config.$floatable || this.$element );
+};
+
+/* Methods */
+
+/**
+ * Set floatable element.
+ *
+ * If an element is already set, it will be cleaned up before setting up the new element.
+ *
+ * @param {jQuery} $floatable Element to make floatable
+ */
+OO.ui.mixin.FloatableElement.prototype.setFloatableElement = function ( $floatable ) {
+ if ( this.$floatable ) {
+ this.$floatable.removeClass( 'oo-ui-floatableElement-floatable' );
+ this.$floatable.css( { left: '', top: '' } );
+ }
+
+ this.$floatable = $floatable.addClass( 'oo-ui-floatableElement-floatable' );
+ this.position();
+};
+
+/**
+ * Set floatable container.
+ *
+ * The element will be always positioned under the specified container.
+ *
+ * @param {jQuery|null} $floatableContainer Container to keep visible, or null to unset
+ */
+OO.ui.mixin.FloatableElement.prototype.setFloatableContainer = function ( $floatableContainer ) {
+ this.$floatableContainer = $floatableContainer;
+ if ( this.$floatable ) {
+ this.position();
+ }
+};
+
+/**
+ * Toggle positioning.
+ *
+ * Do not turn positioning on until after the element is attached to the DOM and visible.
+ *
+ * @param {boolean} [positioning] Enable positioning, omit to toggle
+ * @chainable
+ */
+OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positioning ) {
+ var closestScrollableOfContainer, closestScrollableOfFloatable;
+
+ positioning = positioning === undefined ? !this.positioning : !!positioning;
+
+ if ( this.positioning !== positioning ) {
+ this.positioning = positioning;
+
+ closestScrollableOfContainer = OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 ] );
+ closestScrollableOfFloatable = OO.ui.Element.static.getClosestScrollableContainer( this.$floatable[ 0 ] );
+ if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) {
+ // If the scrollable is the root, we have to listen to scroll events
+ // on the window because of browser inconsistencies (or do we? someone should verify this)
+ if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) {
+ closestScrollableOfContainer = OO.ui.Element.static.getWindow( closestScrollableOfContainer );
+ }
+ }
+
+ if ( positioning ) {
+ this.$floatableWindow = $( this.getElementWindow() );
+ this.$floatableWindow.on( 'resize', this.onFloatableWindowResizeHandler );
+
+ if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) {
+ this.$floatableClosestScrollable = $( closestScrollableOfContainer );
+ this.$floatableClosestScrollable.on( 'scroll', this.onFloatableScrollHandler );
+ }
+
+ // Initial position after visible
+ this.position();
+ } else {
+ this.$floatableWindow.off( 'resize', this.onFloatableWindowResizeHandler );
+ this.$floatableWindow = null;
+
+ if ( this.$floatableClosestScrollable ) {
+ this.$floatableClosestScrollable.off( 'scroll', this.onFloatableScrollHandler );
+ this.$floatableClosestScrollable = null;
+ }
+
+ this.$floatable.css( { left: '', top: '' } );
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Position the floatable below its container.
+ *
+ * This should only be done when both of them are attached to the DOM and visible.
+ *
+ * @chainable
+ */
+OO.ui.mixin.FloatableElement.prototype.position = function () {
+ var pos;
+
+ if ( !this.positioning ) {
+ return this;
+ }
+
+ pos = OO.ui.Element.static.getRelativePosition( this.$floatableContainer, this.$floatable.offsetParent() );
+
+ // Position under container
+ pos.top += this.$floatableContainer.height();
+ this.$floatable.css( pos );
+
+ // We updated the position, so re-evaluate the clipping state.
+ // (ClippableElement does not listen to 'scroll' events on $floatableContainer's parent, and so
+ // will not notice the need to update itself.)
+ // TODO: This is terrible, we shouldn't need to know about ClippableElement at all here. Why does
+ // it not listen to the right events in the right places?
+ if ( this.clip ) {
+ this.clip();
+ }
+
+ return this;
+};
+
/**
* AccessKeyedElement is mixed into other classes to provide an `accesskey` attribute.
* Accesskeys allow an user to go to a specific element by using
/**
* Toggle action layout between vertical and horizontal.
*
- *
* @private
* @param {boolean} [value] Layout actions vertically, omit to toggle
* @chainable
*
* $( 'body' ).append( actionFieldLayout.$element );
*
- *
* @class
* @extends OO.ui.FieldLayout
*
* @param {number} [itemIndex] A specific item to focus on
*/
OO.ui.BookletLayout.prototype.focus = function ( itemIndex ) {
- var $input, page,
+ var page,
items = this.stackLayout.getItems();
if ( itemIndex !== undefined && items[ itemIndex ] ) {
}
// Only change the focus if is not already in the current page
if ( !page.$element.find( ':focus' ).length ) {
- $input = page.$element.find( ':input:first' );
- if ( $input.length ) {
- $input[ 0 ].focus();
- }
+ OO.ui.findFocusable( page.$element ).focus();
}
};
* on it.
*/
OO.ui.BookletLayout.prototype.focusFirstFocusable = function () {
- var i, len,
- found = false,
- items = this.stackLayout.getItems(),
- checkAndFocus = function () {
- if ( OO.ui.isFocusableElement( $( this ) ) ) {
- $( this ).focus();
- found = true;
- return false;
- }
- };
-
- for ( i = 0, len = items.length; i < len; i++ ) {
- if ( found ) {
- break;
- }
- // Find all potentially focusable elements in the item
- // and check if they are focusable
- items[ i ].$element
- .find( 'input, select, textarea, button, object' )
- /* jshint loopfunc:true */
- .each( checkAndFocus );
- }
+ OO.ui.findFocusable( this.stackLayout.$element ).focus();
};
/**
OO.ui.BookletLayout.prototype.setPage = function ( name ) {
var selectedItem,
$focused,
- page = this.pages[ name ];
+ page = this.pages[ name ],
+ previousPage = this.currentPageName && this.pages[ this.currentPageName ];
if ( name !== this.currentPageName ) {
if ( this.outlined ) {
}
}
if ( page ) {
- if ( this.currentPageName && this.pages[ this.currentPageName ] ) {
- this.pages[ this.currentPageName ].setActive( false );
- // Blur anything focused if the next page doesn't have anything focusable - this
- // is not needed if the next page has something focusable because once it is focused
- // this blur happens automatically
- if ( this.autoFocus && !page.$element.find( ':input' ).length ) {
- $focused = this.pages[ this.currentPageName ].$element.find( ':focus' );
+ if ( previousPage ) {
+ previousPage.setActive( false );
+ // Blur anything focused if the next page doesn't have anything focusable.
+ // This is not needed if the next page has something focusable (because once it is focused
+ // this blur happens automatically). If the layout is non-continuous, this check is
+ // meaningless because the next page is not visible yet and thus can't hold focus.
+ if (
+ this.autoFocus &&
+ this.stackLayout.continuous &&
+ OO.ui.findFocusable( page.$element ).length !== 0
+ ) {
+ $focused = previousPage.$element.find( ':focus' );
if ( $focused.length ) {
$focused[ 0 ].blur();
}
}
}
this.currentPageName = name;
- this.stackLayout.setItem( page );
page.setActive( true );
+ this.stackLayout.setItem( page );
+ if ( !this.stackLayout.continuous && previousPage ) {
+ // This should not be necessary, since any inputs on the previous page should have been
+ // blurred when it was hidden, but browsers are not very consistent about this.
+ $focused = previousPage.$element.find( ':focus' );
+ if ( $focused.length ) {
+ $focused[ 0 ].blur();
+ }
+ }
this.emit( 'set', page );
}
}
* }
* OO.inheritClass( CardOneLayout, OO.ui.CardLayout );
* CardOneLayout.prototype.setupTabItem = function () {
- * this.tabItem.setLabel( 'Card One' );
- * };
- *
- * function CardTwoLayout( name, config ) {
- * CardTwoLayout.parent.call( this, name, config );
- * this.$element.append( '<p>Second card</p>' );
- * }
- * OO.inheritClass( CardTwoLayout, OO.ui.CardLayout );
- * CardTwoLayout.prototype.setupTabItem = function () {
- * this.tabItem.setLabel( 'Card Two' );
+ * this.tabItem.setLabel( 'Card one' );
* };
*
* var card1 = new CardOneLayout( 'one' ),
- * card2 = new CardTwoLayout( 'two' );
+ * card2 = new CardLayout( 'two', { label: 'Card two' } );
+ *
+ * card2.$element.append( '<p>Second card</p>' );
*
* var index = new OO.ui.IndexLayout();
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {boolean} [continuous=false] Show all cards, one after another
+ * @cfg {boolean} [expanded=true] Expand the content panel to fill the entire parent element.
* @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new card is displayed.
*/
OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
this.currentCardName = null;
this.cards = {};
this.ignoreFocus = false;
- this.stackLayout = new OO.ui.StackLayout( { continuous: !!config.continuous } );
+ this.stackLayout = new OO.ui.StackLayout( {
+ continuous: !!config.continuous,
+ expanded: config.expanded
+ } );
this.$content.append( this.stackLayout.$element );
this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
* @param {number} [itemIndex] A specific item to focus on
*/
OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
- var $input, card,
+ var card,
items = this.stackLayout.getItems();
if ( itemIndex !== undefined && items[ itemIndex ] ) {
}
// Only change the focus if is not already in the current card
if ( !card.$element.find( ':focus' ).length ) {
- $input = card.$element.find( ':input:first' );
- if ( $input.length ) {
- $input[ 0 ].focus();
- }
+ OO.ui.findFocusable( card.$element ).focus();
}
};
* on it.
*/
OO.ui.IndexLayout.prototype.focusFirstFocusable = function () {
- var i, len,
- found = false,
- items = this.stackLayout.getItems(),
- checkAndFocus = function () {
- if ( OO.ui.isFocusableElement( $( this ) ) ) {
- $( this ).focus();
- found = true;
- return false;
- }
- };
-
- for ( i = 0, len = items.length; i < len; i++ ) {
- if ( found ) {
- break;
- }
- // Find all potentially focusable elements in the item
- // and check if they are focusable
- items[ i ].$element
- .find( 'input, select, textarea, button, object' )
- .each( checkAndFocus );
- }
+ OO.ui.findFocusable( this.stackLayout.$element ).focus();
};
/**
OO.ui.IndexLayout.prototype.setCard = function ( name ) {
var selectedItem,
$focused,
- card = this.cards[ name ];
+ card = this.cards[ name ],
+ previousCard = this.currentCardName && this.cards[ this.currentCardName ];
if ( name !== this.currentCardName ) {
selectedItem = this.tabSelectWidget.getSelectedItem();
this.tabSelectWidget.selectItemByData( name );
}
if ( card ) {
- if ( this.currentCardName && this.cards[ this.currentCardName ] ) {
- this.cards[ this.currentCardName ].setActive( false );
- // Blur anything focused if the next card doesn't have anything focusable - this
- // is not needed if the next card has something focusable because once it is focused
- // this blur happens automatically
- if ( this.autoFocus && !card.$element.find( ':input' ).length ) {
- $focused = this.cards[ this.currentCardName ].$element.find( ':focus' );
+ if ( previousCard ) {
+ previousCard.setActive( false );
+ // Blur anything focused if the next card doesn't have anything focusable.
+ // This is not needed if the next card has something focusable (because once it is focused
+ // this blur happens automatically). If the layout is non-continuous, this check is
+ // meaningless because the next card is not visible yet and thus can't hold focus.
+ if (
+ this.autoFocus &&
+ this.stackLayout.continuous &&
+ OO.ui.findFocusable( card.$element ).length !== 0
+ ) {
+ $focused = previousCard.$element.find( ':focus' );
if ( $focused.length ) {
$focused[ 0 ].blur();
}
}
}
this.currentCardName = name;
- this.stackLayout.setItem( card );
card.setActive( true );
+ this.stackLayout.setItem( card );
+ if ( !this.stackLayout.continuous && previousCard ) {
+ // This should not be necessary, since any inputs on the previous card should have been
+ // blurred when it was hidden, but browsers are not very consistent about this.
+ $focused = previousCard.$element.find( ':focus' );
+ if ( $focused.length ) {
+ $focused[ 0 ].blur();
+ }
+ }
this.emit( 'set', card );
}
}
* @constructor
* @param {string} name Unique symbolic name of card
* @param {Object} [config] Configuration options
+ * @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] Label for card's tab
*/
OO.ui.CardLayout = function OoUiCardLayout( name, config ) {
// Allow passing positional parameters inside the config object
// Properties
this.name = name;
+ this.label = config.label;
this.tabItem = null;
this.active = false;
* @chainable
*/
OO.ui.CardLayout.prototype.setupTabItem = function () {
+ if ( this.label ) {
+ this.tabItem.setLabel( this.label );
+ }
return this;
};
}
if ( this.isClippedHorizontally() ) {
// Anchoring to the right also caused the popup to clip, so just make it fill the container
- containerWidth = this.$clippableContainer.width();
- containerLeft = this.$clippableContainer.offset().left;
+ containerWidth = this.$clippableScrollableContainer.width();
+ containerLeft = this.$clippableScrollableContainer.offset().left;
this.toggleClipping( false );
this.$element.removeClass( 'oo-ui-popupToolGroup-right' );
};
/**
- *
* @private
* Handle outline change events.
*/
/* Methods */
+/**
+ * Construct a OO.ui.CapsuleItemWidget (or a subclass thereof) from given label and data.
+ *
+ * @protected
+ * @param {Mixed} data Custom data of any type.
+ * @param {string} label The label text.
+ * @return {OO.ui.CapsuleItemWidget}
+ */
+OO.ui.CapsuleMultiSelectWidget.prototype.createItemWidget = function ( data, label ) {
+ return new OO.ui.CapsuleItemWidget( { data: data, label: label } );
+};
+
/**
* Get the data of the items in the capsule
* @return {Mixed[]}
}
}
if ( !item ) {
- item = new OO.ui.CapsuleItemWidget( { data: data, label: label } );
+ item = widget.createItemWidget( data, label );
}
widget.addItems( [ item ], i );
} );
if ( !widget.getItemFromData( data ) ) {
item = menu.getItemFromData( data );
if ( item ) {
- items.push( new OO.ui.CapsuleItemWidget( { data: data, label: item.label } ) );
+ items.push( widget.createItemWidget( data, item.label ) );
} else if ( widget.allowArbitrary ) {
- items.push( new OO.ui.CapsuleItemWidget( { data: data, label: String( data ) } ) );
+ items.push( widget.createItemWidget( data, String( data ) ) );
}
}
} );
*
* $( 'body' ).append( dropDown.$element );
*
+ * dropDown.getMenu().selectItemByData( 'b' );
+ *
+ * dropDown.getMenu().getSelectedItem().getData(); // returns 'b'
+ *
* For more information, please see the [OOjs UI documentation on MediaWiki] [1].
*
* [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
* @protected
*/
OO.ui.SelectFileWidget.prototype.updateUI = function () {
+ var $label;
if ( !this.isSupported ) {
this.$element.addClass( 'oo-ui-selectFileWidget-notsupported' );
this.$element.removeClass( 'oo-ui-selectFileWidget-empty' );
this.$element.addClass( 'oo-ui-selectFileWidget-supported' );
if ( this.currentFile ) {
this.$element.removeClass( 'oo-ui-selectFileWidget-empty' );
- this.setLabel( this.currentFile.name +
- ( this.currentFile.type !== '' ? OO.ui.msg( 'ooui-semicolon-separator' ) + this.currentFile.type : '' )
- );
+ $label = $( [] );
+ if ( this.currentFile.type !== '' ) {
+ $label = $label.add( $( '<span>' ).addClass( 'oo-ui-selectFileWidget-fileType' ).text( this.currentFile.type ) );
+ }
+ $label = $label.add( $( '<span>' ).text( this.currentFile.name ) );
+ this.setLabel( $label );
} else {
this.$element.addClass( 'oo-ui-selectFileWidget-empty' );
this.setLabel( this.placeholder );
/**
* Handle mouse click events.
*
- *
* @private
* @param {jQuery.Event} e Mouse click event
*/
/**
* Handle key press events.
*
- *
* @private
* @param {jQuery.Event} e Key press event
*/
* ] );
* $( 'body' ).append( fieldset.$element );
*
- *
* @class
* @extends OO.ui.Widget
* @mixins OO.ui.mixin.LabelElement
* } );
* $( 'body' ).append( myDropdown.$element );
*
- *
* @class
* @extends OO.ui.DecoratedOptionWidget
*
* @class
* @extends OO.ui.Widget
* @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.ClippableElement
*
* @constructor
* @param {Object} [config] Configuration options
*
* @class
* @extends OO.ui.MenuSelectWidget
+ * @mixins OO.ui.mixin.FloatableElement
*
* @constructor
* @param {OO.ui.Widget} [inputWidget] Widget to provide the menu for.
// Parent constructor
OO.ui.FloatingMenuSelectWidget.parent.call( this, config );
- // Properties
+ // Properties (must be set before mixin constructors)
this.inputWidget = inputWidget; // For backwards compatibility
this.$container = config.$container || this.inputWidget.$element;
- this.onWindowResizeHandler = this.onWindowResize.bind( this );
+
+ // Mixins constructors
+ OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, { $floatableContainer: this.$container } ) );
// Initialization
this.$element.addClass( 'oo-ui-floatingMenuSelectWidget' );
/* Setup */
OO.inheritClass( OO.ui.FloatingMenuSelectWidget, OO.ui.MenuSelectWidget );
+OO.mixinClass( OO.ui.FloatingMenuSelectWidget, OO.ui.mixin.FloatableElement );
// For backwards compatibility
OO.ui.TextInputMenuSelectWidget = OO.ui.FloatingMenuSelectWidget;
/* Methods */
-/**
- * Handle window resize event.
- *
- * @private
- * @param {jQuery.Event} e Window resize event
- */
-OO.ui.FloatingMenuSelectWidget.prototype.onWindowResize = function () {
- this.position();
-};
-
/**
* @inheritdoc
*/
OO.ui.FloatingMenuSelectWidget.prototype.toggle = function ( visible ) {
var change;
visible = visible === undefined ? !this.isVisible() : !!visible;
-
change = visible !== this.isVisible();
if ( change && visible ) {
// Make sure the width is set before the parent method runs.
- // After this we have to call this.position(); again to actually
- // position ourselves correctly.
- this.position();
+ this.setIdealSize( this.$container.width() );
}
// Parent method
+ // This will call this.clip(), which is nonsensical since we're not positioned yet...
OO.ui.FloatingMenuSelectWidget.parent.prototype.toggle.call( this, visible );
if ( change ) {
- if ( this.isVisible() ) {
- this.position();
- $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler );
- } else {
- $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
- }
+ this.togglePositioning( this.isVisible() );
}
return this;
};
-/**
- * Position the menu.
- *
- * @private
- * @chainable
- */
-OO.ui.FloatingMenuSelectWidget.prototype.position = function () {
- var $container = this.$container,
- pos = OO.ui.Element.static.getRelativePosition( $container, this.$element.offsetParent() );
-
- // Position under input
- pos.top += $container.height();
- this.$element.css( pos );
-
- // Set width
- this.setIdealSize( $container.width() );
- // We updated the position, so re-evaluate the clipping state
- this.clip();
-
- return this;
-};
-
/**
* OutlineSelectWidget is a structured list that contains {@link OO.ui.OutlineOptionWidget outline options}
* A set of controls can be provided with an {@link OO.ui.OutlineControlsWidget outline controls} widget.
/**
* Handle key down events.
*
- *
* @private
* @param {jQuery.Event} e Key down event
*/