diff --git a/VisualEditor.i18n.php b/VisualEditor.i18n.php
index 01962b0bbb..86aa9a03d9 100644
--- a/VisualEditor.i18n.php
+++ b/VisualEditor.i18n.php
@@ -22,6 +22,8 @@ $messages['en'] = array(
'ooui-dialog-action-close' => 'Close',
'ooui-outline-control-move-down' => 'Move item down',
'ooui-outline-control-move-up' => 'Move item up',
+ 'ooui-toggle-on' => 'On',
+ 'ooui-toggle-off' => 'Off',
'ooui-toolbar-more' => 'More',
'tag-visualeditor' => '[[{{MediaWiki:visualeditor-descriptionpagelink}}|VisualEditor]]',
'tag-visualeditor-description' => 'Edit made using the [[{{MediaWiki:visualeditor-descriptionpagelink}}|VisualEditor]]',
@@ -253,6 +255,8 @@ $messages['qqq'] = array(
{{Identical|Close}}',
'ooui-outline-control-move-down' => 'Tool tip for a button that moves items in a list down one place',
'ooui-outline-control-move-up' => 'Tool tip for a button that moves items in a list up one place',
+ 'ooui-toggle-on' => 'Label for toggle on state',
+ 'ooui-toggle-off' => 'Label for toggle off state',
'ooui-toolbar-more' => 'Label for the toolbar group that contains a list of all other available tools.
{{Identical|More}}',
'tag-visualeditor' => 'Short description of the visualeditor tag.
diff --git a/VisualEditor.php b/VisualEditor.php
index 6f54118601..04e770565b 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -145,6 +145,7 @@ $wgResourceModules += array(
'oojs-ui/widgets/OO.ui.SearchWidget.js',
'oojs-ui/widgets/OO.ui.TextInputWidget.js',
'oojs-ui/widgets/OO.ui.TextInputMenuWidget.js',
+ 'oojs-ui/widgets/OO.ui.ToggleWidget.js',
),
'styles' => array(
'oojs-ui/styles/OO.ui.css',
@@ -162,6 +163,8 @@ $wgResourceModules += array(
'ooui-dialog-action-close',
'ooui-outline-control-move-down',
'ooui-outline-control-move-up',
+ 'ooui-toggle-on',
+ 'ooui-toggle-off',
'ooui-toolbar-more',
),
'dependencies' => array(
diff --git a/demos/ve/index.php b/demos/ve/index.php
index 07cceb2a1b..611f0bca69 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -154,6 +154,7 @@ $html = file_get_contents( $page );
+
diff --git a/modules/oojs-ui/OO.ui.js b/modules/oojs-ui/OO.ui.js
index 4bba1844e3..6258a7c47e 100644
--- a/modules/oojs-ui/OO.ui.js
+++ b/modules/oojs-ui/OO.ui.js
@@ -49,6 +49,10 @@ var messages = {
'ooui-outline-control-move-down': 'Move item down',
// Tool tip for a button that moves items in a list up one place
'ooui-outline-control-move-up': 'Move item up',
+ // Label for toggle on state
+ 'ooui-toggle-on': 'On',
+ // Label for toggle off state
+ 'ooui-toggle-off': 'Off',
// Label for the toolbar group that contains a list of all other available tools
'ooui-toolbar-more': 'More'
};
diff --git a/modules/oojs-ui/styles/OO.ui.Widget.css b/modules/oojs-ui/styles/OO.ui.Widget.css
index e73a722589..c314f2f92b 100644
--- a/modules/oojs-ui/styles/OO.ui.Widget.css
+++ b/modules/oojs-ui/styles/OO.ui.Widget.css
@@ -553,3 +553,121 @@ a.oo-ui-buttonWidget-button {
overflow-y: auto;
line-height: 0;
}
+
+/* OO.ui.ToggleWidget */
+
+.oo-ui-toggleWidget {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ height: 2em;
+ width: 6em;
+ border-radius: 0.5em;
+ overflow: hidden;
+ box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #ddd;
+ border: solid 1px #ccc;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ cursor: pointer;
+ -webkit-transition: background-color 200ms;
+ -moz-transition: background-color 200ms;
+ -o-transition: background-color 200ms;
+ transition: background-color 200ms;
+}
+
+.oo-ui-toggleWidget-slide {
+ position: absolute;
+ top: 0;
+ display: block;
+ height: 2em;
+ width: 10em;
+ -webkit-transition: left 200ms ease-in-out, margin-left 200ms ease-in-out;
+ -moz-transition: left 200ms ease-in-out, margin-left 200ms ease-in-out;
+ -o-transition: left 200ms ease-in-out, margin-left 200ms ease-in-out;
+ transition: left 200ms ease-in-out, margin-left 200ms ease-in-out;
+}
+
+.oo-ui-toggleWidget-dragging .oo-ui-toggleWidget-slide {
+ -webkit-transition: left 200ms ease-in-out;
+ -moz-transition: left 200ms ease-in-out;
+ -o-transition: left 200ms ease-in-out;
+ transition: left 200ms ease-in-out;
+}
+
+.oo-ui-toggleWidget-on .oo-ui-toggleWidget-slide {
+ left: 0;
+}
+
+.oo-ui-toggleWidget-off .oo-ui-toggleWidget-slide {
+ left: -4em;
+}
+
+.oo-ui-toggleWidget-grip {
+ position: absolute;
+ top: 0;
+ left: 4em;
+ display: block;
+ width: 2em;
+ height: 2em;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ margin: -1px;
+ box-shadow: 0 0.1em 0.25em rgba(0, 0, 0, 0.1);
+ border-radius: 0.5em;
+
+ /* Gray */
+ border: 1px #c9c9c9 solid;
+ background-color: #ffffff;
+ filter: progid:DXImageTransform.Microsoft.gradient(
+ GradientType=0,startColorstr=#ffffff, endColorstr=#f0f0f0
+ );
+ background-image: -webkit-gradient(
+ linear, right top, right bottom, color-stop(0%,#ffffff), color-stop(100%,#f0f0f0)
+ );
+ background-image: -webkit-linear-gradient(top, #ffffff 0%, #f0f0f0 100%);
+ background-image: -moz-linear-gradient(top, #ffffff 0%, #f0f0f0 100%);
+ background-image: -ms-linear-gradient(top, #ffffff 0%, #f0f0f0 100%);
+ background-image: -o-linear-gradient(top, #ffffff 0%, #f0f0f0 100%);
+ background-image: linear-gradient(top, #ffffff 0%, #f0f0f0 100%);
+}
+
+.oo-ui-toggleWidget:hover,
+.oo-ui-toggleWidget-dragging,
+.oo-ui-toggleWidget:hover .oo-ui-toggleWidget-grip,
+.oo-ui-toggleWidget-dragging .oo-ui-toggleWidget-grip {
+ border-color: #aaa;
+}
+
+.oo-ui-toggleWidget-label {
+ position: absolute;
+ top: 0;
+ display: block;
+ width: 5em;
+ height: 2em;
+ line-height: 2em;
+ color: black;
+ text-align: center;
+ cursor: pointer;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.oo-ui-toggleWidget-label-on {
+ left: -0.5em;
+ padding: 0 0.5em 0 0.75em;
+ background-color: rgba(0,255,0,0.25);
+}
+
+.oo-ui-toggleWidget-label-off {
+ left: 5.5em;
+ padding: 0 0.75em 0 0.5em;
+ background-color: rgba(128,128,128,0.25);
+}
diff --git a/modules/oojs-ui/widgets/OO.ui.ToggleWidget.js b/modules/oojs-ui/widgets/OO.ui.ToggleWidget.js
new file mode 100644
index 0000000000..2cffc81f3c
--- /dev/null
+++ b/modules/oojs-ui/widgets/OO.ui.ToggleWidget.js
@@ -0,0 +1,175 @@
+/*!
+ * ObjectOriented UserInterface ToggleWidget class.
+ *
+ * @copyright 2011-2013 OOJS Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Creates an OO.ui.ToggleWidget object.
+ *
+ * @class
+ * @abstract
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [value=false] Initial value
+ * @cfg {string} [onLabel='On'] Label for on state
+ * @cfg {string} [offLabel='Off'] Label for off state
+ */
+OO.ui.ToggleWidget = function OoUiToggleWidget( config ) {
+ // Configuration initialization
+ config = $.extend( {
+ 'onLabel': OO.ui.msg( 'ooui-toggle-on' ),
+ 'offLabel': OO.ui.msg( 'ooui-toggle-on' )
+ }, config );
+
+ // Parent constructor
+ OO.ui.Widget.call( this, config );
+
+ // Properties
+ this.value = null;
+ this.dragging = false;
+ this.dragStart = null;
+ this.sliding = false;
+ this.$slide = this.$( '' );
+ this.$grip = this.$( '' );
+ this.$onLabel = this.$( '' );
+ this.$offLabel = this.$( '' );
+ this.onDocumentMouseMoveHandler = OO.ui.bind( this.onDocumentMouseMove, this );
+ this.onDocumentMouseUpHandler = OO.ui.bind( this.onDocumentMouseUp, this );
+
+ // Events
+ this.$slide.on( 'mousedown', OO.ui.bind( this.onMouseDown, this ) );
+
+ // Initialization
+ this.$grip.addClass( 'oo-ui-toggleWidget-grip' );
+ this.$onLabel
+ .addClass( 'oo-ui-toggleWidget-label oo-ui-toggleWidget-label-on' )
+ .text( config.onLabel || '' );
+ this.$offLabel
+ .addClass( 'oo-ui-toggleWidget-label oo-ui-toggleWidget-label-off' )
+ .text( config.offLabel || '' );
+ this.$slide
+ .addClass( 'oo-ui-toggleWidget-slide' )
+ .append( this.$onLabel, this.$offLabel, this.$grip );
+ this.$element
+ .addClass( 'oo-ui-toggleWidget' )
+ .append( this.$slide );
+ this.setValue( !!config.value );
+};
+
+/* Inheritance */
+
+OO.inheritClass( OO.ui.ToggleWidget, OO.ui.Widget );
+
+/* Events */
+
+/**
+ * @event change
+ * @param {boolean} value Changed value
+ */
+
+/* Methods */
+
+/**
+ * Handles mouse down events.
+ *
+ * @method
+ * @param {jQuery.Event} e Mouse down event
+ */
+OO.ui.ToggleWidget.prototype.onMouseDown = function ( e ) {
+ if ( e.which === 1 ) {
+ this.dragging = true;
+ this.dragStart = e.pageX;
+ this.$( this.$.context ).on( {
+ 'mousemove': this.onDocumentMouseMoveHandler,
+ 'mouseup': this.onDocumentMouseUpHandler
+ } );
+ this.$element.addClass( 'oo-ui-toggleWidget-dragging' );
+ return false;
+ }
+};
+
+/**
+ * Handles document mouse up events.
+ *
+ * @method
+ * @param {jQuery.Event} e Mouse up event
+ */
+OO.ui.ToggleWidget.prototype.onDocumentMouseUp = function ( e ) {
+ var overlap, dragOffset;
+
+ if ( e.which === 1 ) {
+ this.$element.removeClass( 'oo-ui-toggleWidget-dragging' );
+
+ if ( !this.sliding ) {
+ this.setValue( !this.value );
+ } else {
+ this.$slide.css( 'margin-left', 0 );
+ dragOffset = e.pageX - this.dragStart;
+ overlap = this.$element.outerWidth() - this.$slide.outerWidth();
+ if ( this.value ? overlap / 2 > dragOffset : -overlap / 2 < dragOffset ) {
+ this.setValue( !this.value );
+ }
+ }
+ this.dragging = false;
+ this.sliding = false;
+ this.$( this.$.context ).off( {
+ 'mousemove': this.onDocumentMouseMoveHandler,
+ 'mouseup': this.onDocumentMouseUpHandler
+ } );
+ }
+};
+
+/**
+ * Handles document mouse move events.
+ *
+ * @method
+ * @param {jQuery.Event} e Mouse move event
+ */
+OO.ui.ToggleWidget.prototype.onDocumentMouseMove = function ( e ) {
+ var overlap, dragOffset, left;
+
+ if ( this.dragging ) {
+ dragOffset = e.pageX - this.dragStart;
+ if ( dragOffset !== 0 || this.sliding ) {
+ this.sliding = true;
+ overlap = this.$element.outerWidth() - this.$slide.outerWidth();
+ left = this.value ?
+ Math.min( 0, Math.max( overlap, dragOffset ) ) :
+ Math.min( -overlap, Math.max( 0, dragOffset ) );
+ this.$slide.css( 'margin-left', left );
+ }
+ }
+};
+
+/**
+ * Get the value of the toggle.
+ *
+ * @method
+ * @returns {boolean} Toggle value
+ */
+OO.ui.ToggleWidget.prototype.getValue = function () {
+ return this.value;
+};
+
+/**
+ * Set the value of the toggle.
+ *
+ * @method
+ * @param {boolean} value New value
+ * @fires change
+ * @chainable
+ */
+OO.ui.ToggleWidget.prototype.setValue = function ( value ) {
+ if ( this.value !== value ) {
+ this.value = value;
+ this.$element
+ .toggleClass( 'oo-ui-toggleWidget-on', value )
+ .toggleClass( 'oo-ui-toggleWidget-off', !value );
+ this.emit( 'change', this.value );
+ }
+ return this;
+};
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 0df4cc2b0f..f38be30ab1 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -98,6 +98,7 @@
+