diff options
Diffstat (limited to 'vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js')
-rw-r--r-- | vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js | 535 |
1 files changed, 0 insertions, 535 deletions
diff --git a/vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js b/vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js deleted file mode 100644 index 1ba26641..00000000 --- a/vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js +++ /dev/null @@ -1,535 +0,0 @@ -/** - * TextInputWidgets, like HTML text inputs, can be configured with options that customize the - * size of the field as well as its presentation. In addition, these widgets can be configured - * with {@link OO.ui.IconElement icons}, {@link OO.ui.IndicatorElement indicators}, an optional - * validation-pattern (used to determine if an input value is valid or not) and an input filter, - * which modifies incoming values rather than validating them. - * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples. - * - * This widget can be used inside a HTML form, such as a OO.ui.FormLayout. - * - * @example - * // Example of a text input widget - * var textInput = new OO.ui.TextInputWidget( { - * value: 'Text input' - * } ) - * $( 'body' ).append( textInput.$element ); - * - * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs - * - * @class - * @extends OO.ui.InputWidget - * @mixins OO.ui.IconElement - * @mixins OO.ui.IndicatorElement - * @mixins OO.ui.PendingElement - * @mixins OO.ui.LabelElement - * - * @constructor - * @param {Object} [config] Configuration options - * @cfg {string} [type='text'] The value of the HTML `type` attribute - * @cfg {string} [placeholder] Placeholder text - * @cfg {boolean} [autofocus=false] Use an HTML `autofocus` attribute to - * instruct the browser to focus this widget. - * @cfg {boolean} [readOnly=false] Prevent changes to the value of the text input. - * @cfg {number} [maxLength] Maximum number of characters allowed in the input. - * @cfg {boolean} [multiline=false] Allow multiple lines of text - * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content. - * Use the #maxRows config to specify a maximum number of displayed rows. - * @cfg {boolean} [maxRows=10] Maximum number of rows to display when #autosize is set to true. - * @cfg {string} [labelPosition='after'] The position of the inline label relative to that of - * the value or placeholder text: `'before'` or `'after'` - * @cfg {boolean} [required=false] Mark the field as required - * @cfg {RegExp|Function|string} [validate] Validation pattern: when string, a symbolic name of a - * pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' - * (the value must contain only numbers); when RegExp, a regular expression that must match the - * value for it to be considered valid; when Function, a function receiving the value as parameter - * that must return true, or promise resolving to true, for it to be considered valid. - */ -OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { - // Configuration initialization - config = $.extend( { - type: 'text', - labelPosition: 'after', - maxRows: 10 - }, config ); - - // Parent constructor - OO.ui.TextInputWidget.super.call( this, config ); - - // Mixin constructors - OO.ui.IconElement.call( this, config ); - OO.ui.IndicatorElement.call( this, config ); - OO.ui.PendingElement.call( this, config ); - OO.ui.LabelElement.call( this, config ); - - // Properties - this.readOnly = false; - this.multiline = !!config.multiline; - this.autosize = !!config.autosize; - this.maxRows = config.maxRows; - this.validate = null; - - // Clone for resizing - if ( this.autosize ) { - this.$clone = this.$input - .clone() - .insertAfter( this.$input ) - .attr( 'aria-hidden', 'true' ) - .addClass( 'oo-ui-element-hidden' ); - } - - this.setValidation( config.validate ); - this.setLabelPosition( config.labelPosition ); - - // Events - this.$input.on( { - keypress: this.onKeyPress.bind( this ), - blur: this.onBlur.bind( this ) - } ); - this.$input.one( { - focus: this.onElementAttach.bind( this ) - } ); - this.$icon.on( 'mousedown', this.onIconMouseDown.bind( this ) ); - this.$indicator.on( 'mousedown', this.onIndicatorMouseDown.bind( this ) ); - this.on( 'labelChange', this.updatePosition.bind( this ) ); - this.connect( this, { change: 'onChange' } ); - - // Initialization - this.$element - .addClass( 'oo-ui-textInputWidget' ) - .append( this.$icon, this.$indicator ); - this.setReadOnly( !!config.readOnly ); - if ( config.placeholder ) { - this.$input.attr( 'placeholder', config.placeholder ); - } - if ( config.maxLength !== undefined ) { - this.$input.attr( 'maxlength', config.maxLength ); - } - if ( config.autofocus ) { - this.$input.attr( 'autofocus', 'autofocus' ); - } - if ( config.required ) { - this.$input.attr( 'required', 'required' ); - this.$input.attr( 'aria-required', 'true' ); - } - if ( this.label || config.autosize ) { - this.installParentChangeDetector(); - } -}; - -/* Setup */ - -OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget ); -OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IconElement ); -OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IndicatorElement ); -OO.mixinClass( OO.ui.TextInputWidget, OO.ui.PendingElement ); -OO.mixinClass( OO.ui.TextInputWidget, OO.ui.LabelElement ); - -/* Static properties */ - -OO.ui.TextInputWidget.static.validationPatterns = { - 'non-empty': /.+/, - integer: /^\d+$/ -}; - -/* Events */ - -/** - * An `enter` event is emitted when the user presses 'enter' inside the text box. - * - * Not emitted if the input is multiline. - * - * @event enter - */ - -/* Methods */ - -/** - * Handle icon mouse down events. - * - * @private - * @param {jQuery.Event} e Mouse down event - * @fires icon - */ -OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) { - if ( e.which === 1 ) { - this.$input[ 0 ].focus(); - return false; - } -}; - -/** - * Handle indicator mouse down events. - * - * @private - * @param {jQuery.Event} e Mouse down event - * @fires indicator - */ -OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) { - if ( e.which === 1 ) { - this.$input[ 0 ].focus(); - return false; - } -}; - -/** - * Handle key press events. - * - * @private - * @param {jQuery.Event} e Key press event - * @fires enter If enter key is pressed and input is not multiline - */ -OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) { - if ( e.which === OO.ui.Keys.ENTER && !this.multiline ) { - this.emit( 'enter', e ); - } -}; - -/** - * Handle blur events. - * - * @private - * @param {jQuery.Event} e Blur event - */ -OO.ui.TextInputWidget.prototype.onBlur = function () { - this.setValidityFlag(); -}; - -/** - * Handle element attach events. - * - * @private - * @param {jQuery.Event} e Element attach event - */ -OO.ui.TextInputWidget.prototype.onElementAttach = function () { - // Any previously calculated size is now probably invalid if we reattached elsewhere - this.valCache = null; - this.adjustSize(); - this.positionLabel(); -}; - -/** - * Handle change events. - * - * @param {string} value - * @private - */ -OO.ui.TextInputWidget.prototype.onChange = function () { - this.setValidityFlag(); - this.adjustSize(); -}; - -/** - * Check if the input is {@link #readOnly read-only}. - * - * @return {boolean} - */ -OO.ui.TextInputWidget.prototype.isReadOnly = function () { - return this.readOnly; -}; - -/** - * Set the {@link #readOnly read-only} state of the input. - * - * @param {boolean} state Make input read-only - * @chainable - */ -OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) { - this.readOnly = !!state; - this.$input.prop( 'readOnly', this.readOnly ); - return this; -}; - -/** - * Support function for making #onElementAttach work across browsers. - * - * This whole function could be replaced with one line of code using the DOMNodeInsertedIntoDocument - * event, but it's not supported by Firefox and allegedly deprecated, so we only use it as fallback. - * - * Due to MutationObserver performance woes, #onElementAttach is only somewhat reliably called the - * first time that the element gets attached to the documented. - */ -OO.ui.TextInputWidget.prototype.installParentChangeDetector = function () { - var mutationObserver, onRemove, topmostNode, fakeParentNode, - MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, - widget = this; - - if ( MutationObserver ) { - // The new way. If only it wasn't so ugly. - - if ( this.$element.closest( 'html' ).length ) { - // Widget is attached already, do nothing. This breaks the functionality of this function when - // the widget is detached and reattached. Alas, doing this correctly with MutationObserver - // would require observation of the whole document, which would hurt performance of other, - // more important code. - return; - } - - // Find topmost node in the tree - topmostNode = this.$element[0]; - while ( topmostNode.parentNode ) { - topmostNode = topmostNode.parentNode; - } - - // We have no way to detect the $element being attached somewhere without observing the entire - // DOM with subtree modifications, which would hurt performance. So we cheat: we hook to the - // parent node of $element, and instead detect when $element is removed from it (and thus - // probably attached somewhere else). If there is no parent, we create a "fake" one. If it - // doesn't get attached, we end up back here and create the parent. - - mutationObserver = new MutationObserver( function ( mutations ) { - var i, j, removedNodes; - for ( i = 0; i < mutations.length; i++ ) { - removedNodes = mutations[ i ].removedNodes; - for ( j = 0; j < removedNodes.length; j++ ) { - if ( removedNodes[ j ] === topmostNode ) { - setTimeout( onRemove, 0 ); - return; - } - } - } - } ); - - onRemove = function () { - // If the node was attached somewhere else, report it - if ( widget.$element.closest( 'html' ).length ) { - widget.onElementAttach(); - } - mutationObserver.disconnect(); - widget.installParentChangeDetector(); - }; - - // Create a fake parent and observe it - fakeParentNode = $( '<div>' ).append( this.$element )[0]; - mutationObserver.observe( fakeParentNode, { childList: true } ); - } else { - // Using the DOMNodeInsertedIntoDocument event is much nicer and less magical, and works for - // detachment and reattachment, but it's not supported by Firefox and allegedly deprecated. - this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) ); - } -}; - -/** - * Automatically adjust the size of the text input. - * - * This only affects #multiline inputs that are {@link #autosize autosized}. - * - * @chainable - */ -OO.ui.TextInputWidget.prototype.adjustSize = function () { - var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight; - - if ( this.multiline && this.autosize && this.$input.val() !== this.valCache ) { - this.$clone - .val( this.$input.val() ) - .attr( 'rows', '' ) - // Set inline height property to 0 to measure scroll height - .css( 'height', 0 ); - - this.$clone.removeClass( 'oo-ui-element-hidden' ); - - this.valCache = this.$input.val(); - - scrollHeight = this.$clone[ 0 ].scrollHeight; - - // Remove inline height property to measure natural heights - this.$clone.css( 'height', '' ); - innerHeight = this.$clone.innerHeight(); - outerHeight = this.$clone.outerHeight(); - - // Measure max rows height - this.$clone - .attr( 'rows', this.maxRows ) - .css( 'height', 'auto' ) - .val( '' ); - maxInnerHeight = this.$clone.innerHeight(); - - // Difference between reported innerHeight and scrollHeight with no scrollbars present - // Equals 1 on Blink-based browsers and 0 everywhere else - measurementError = maxInnerHeight - this.$clone[ 0 ].scrollHeight; - idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError ); - - this.$clone.addClass( 'oo-ui-element-hidden' ); - - // Only apply inline height when expansion beyond natural height is needed - if ( idealHeight > innerHeight ) { - // Use the difference between the inner and outer height as a buffer - this.$input.css( 'height', idealHeight + ( outerHeight - innerHeight ) ); - } else { - this.$input.css( 'height', '' ); - } - } - return this; -}; - -/** - * @inheritdoc - * @private - */ -OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) { - return config.multiline ? $( '<textarea>' ) : $( '<input type="' + config.type + '" />' ); -}; - -/** - * Check if the input supports multiple lines. - * - * @return {boolean} - */ -OO.ui.TextInputWidget.prototype.isMultiline = function () { - return !!this.multiline; -}; - -/** - * Check if the input automatically adjusts its size. - * - * @return {boolean} - */ -OO.ui.TextInputWidget.prototype.isAutosizing = function () { - return !!this.autosize; -}; - -/** - * Select the entire text of the input. - * - * @chainable - */ -OO.ui.TextInputWidget.prototype.select = function () { - this.$input.select(); - return this; -}; - -/** - * Set the validation pattern. - * - * The validation pattern is either a regular expression, a function, or the symbolic name of a - * pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' (the - * value must contain only numbers). - * - * @param {RegExp|Function|string|null} validate Regular expression, function, or the symbolic name - * of a pattern (either ‘integer’ or ‘non-empty’) defined by the class. - */ -OO.ui.TextInputWidget.prototype.setValidation = function ( validate ) { - if ( validate instanceof RegExp || validate instanceof Function ) { - this.validate = validate; - } else { - this.validate = this.constructor.static.validationPatterns[ validate ] || /.*/; - } -}; - -/** - * Sets the 'invalid' flag appropriately. - * - * @param {boolean} [isValid] Optionally override validation result - */ -OO.ui.TextInputWidget.prototype.setValidityFlag = function ( isValid ) { - var widget = this, - setFlag = function ( valid ) { - if ( !valid ) { - widget.$input.attr( 'aria-invalid', 'true' ); - } else { - widget.$input.removeAttr( 'aria-invalid' ); - } - widget.setFlags( { invalid: !valid } ); - }; - - if ( isValid !== undefined ) { - setFlag( isValid ); - } else { - this.isValid().done( setFlag ); - } -}; - -/** - * Check if a value is valid. - * - * This method returns a promise that resolves with a boolean `true` if the current value is - * considered valid according to the supplied {@link #validate validation pattern}. - * - * @return {jQuery.Promise} A promise that resolves to a boolean `true` if the value is valid. - */ -OO.ui.TextInputWidget.prototype.isValid = function () { - if ( this.validate instanceof Function ) { - var result = this.validate( this.getValue() ); - if ( $.isFunction( result.promise ) ) { - return result.promise(); - } else { - return $.Deferred().resolve( !!result ).promise(); - } - } else { - return $.Deferred().resolve( !!this.getValue().match( this.validate ) ).promise(); - } -}; - -/** - * Set the position of the inline label relative to that of the value: `‘before’` or `‘after’`. - * - * @param {string} labelPosition Label position, 'before' or 'after' - * @chainable - */ -OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) { - this.labelPosition = labelPosition; - this.updatePosition(); - return this; -}; - -/** - * Deprecated alias of #setLabelPosition - * - * @deprecated Use setLabelPosition instead. - */ -OO.ui.TextInputWidget.prototype.setPosition = - OO.ui.TextInputWidget.prototype.setLabelPosition; - -/** - * Update the position of the inline label. - * - * This method is called by #setLabelPosition, and can also be called on its own if - * something causes the label to be mispositioned. - * - * - * @chainable - */ -OO.ui.TextInputWidget.prototype.updatePosition = function () { - var after = this.labelPosition === 'after'; - - this.$element - .toggleClass( 'oo-ui-textInputWidget-labelPosition-after', !!this.label && after ) - .toggleClass( 'oo-ui-textInputWidget-labelPosition-before', !!this.label && !after ); - - if ( this.label ) { - this.positionLabel(); - } - - return this; -}; - -/** - * Position the label by setting the correct padding on the input. - * - * @private - * @chainable - */ -OO.ui.TextInputWidget.prototype.positionLabel = function () { - // Clear old values - this.$input - // Clear old values if present - .css( { - 'padding-right': '', - 'padding-left': '' - } ); - - if ( this.label ) { - this.$element.append( this.$label ); - } else { - this.$label.detach(); - return; - } - - var after = this.labelPosition === 'after', - rtl = this.$element.css( 'direction' ) === 'rtl', - property = after === rtl ? 'padding-left' : 'padding-right'; - - this.$input.css( property, this.$label.outerWidth( true ) ); - - return this; -}; |