mediawiki-extensions-Visual.../modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineParameterSelectWidget.js
Thiemo Kreuz 19edc6043a Tab to first template parameter in list, not to the selection
This is a partial revert of Iaf089f4. It restores the old behavior:
* In case there is already a highlight in the parameter list, just
  keep that. Usually there is no highlight at this point, but better
  have this check in place to be sure.
* Otherwise always start at the top.

Jumping to the selection is confusing, esp. for keyboard-only users.
The argument goes like this:
* Let's say I'm in the middle of editing values on the right side of
  the dialog.
* I want to navigate to the sidebar. How do I do this with the
  keyboard? I use the tab key.
* Pressing tab also implies I move the selection to the next
  parameter. And the next. Until I reach the end of the parameter
  list. Then the selection stays there.
* When I finally reach the sidebar and tab into the parameter list,
  the last parameter is selected. But this was merely a side-effect
  of me navigating the dialog.

Such a "selection becomes highlighting" behavior was not specified
in T311204.

This patch is requested and approved by PM.

Bug: T312647
Bug: T311204
Change-Id: Ie5b5dfd4fca132050815e6182845ca23adb5f805
2022-07-21 10:44:48 +02:00

260 lines
7.7 KiB
JavaScript

/**
* List of template parameters, each of which can be added or removed using a
* checkbox.
*
* This is modelled after {@see OO.ui.OutlineSelectWidget}. Currently we use
* the SelectWidget in multi-select mode, and selection maps to checked
* checkboxes.
*
* @class
* @extends OO.ui.SelectWidget
*
* @constructor
* @param {Object} config
* @cfg {ve.ui.MWTransclusionOutlineParameterWidget[]} items
* @property {string|null} activeParameter Name of the currently selected parameter
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget = function VeUiMWTransclusionOutlineParameterSelectWidget( config ) {
// Parent constructor
ve.ui.MWTransclusionOutlineParameterSelectWidget.super.call( this, ve.extendObject( config, {
classes: [ 've-ui-mwTransclusionOutlineParameterSelectWidget' ],
multiselect: true
} ) );
// Mixin constructors
OO.ui.mixin.TabIndexedElement.call( this, {
tabIndex: this.isEmpty() ? -1 : 0
} );
ve.ui.MWAriaDescribe.call( this, config );
this.$element
.on( {
focus: this.bindDocumentKeyDownListener.bind( this ),
blur: this.onBlur.bind( this )
} );
this.activeParameter = null;
};
/* Inheritance */
OO.inheritClass( ve.ui.MWTransclusionOutlineParameterSelectWidget, OO.ui.SelectWidget );
OO.mixinClass( ve.ui.MWTransclusionOutlineParameterSelectWidget, OO.ui.mixin.TabIndexedElement );
OO.mixinClass( ve.ui.MWTransclusionOutlineParameterSelectWidget, ve.ui.MWAriaDescribe );
/* Events */
/**
* This is fired instead of the "choose" event from the {@see OO.ui.SelectWidget} base class when
* pressing space on a parameter to toggle it or scroll it into view, without losing the focus.
*
* @event templateParameterSpaceDown
* @param {ve.ui.MWTransclusionOutlineParameterWidget} item
* @param {boolean} selected
*/
/* Static Methods */
/**
* @param {Object} config
* @param {string} config.data Parameter name
* @param {string} config.label
* @param {boolean} [config.required=false] Required parameters can't be unchecked
* @param {boolean} [config.selected=false] If the parameter is currently used (checked)
* @return {ve.ui.MWTransclusionOutlineParameterWidget}
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.static.createItem = function ( config ) {
return new ve.ui.MWTransclusionOutlineParameterWidget( config );
};
/* Methods */
/**
* @inheritDoc OO.ui.mixin.GroupElement
* @param {ve.ui.MWTransclusionOutlineParameterWidget[]} items
* @param {number} [index]
* @return {ve.ui.MWTransclusionOutlineParameterSelectWidget}
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.addItems = function ( items, index ) {
var self = this;
items.forEach( function ( item ) {
item.connect( self, {
change: [ 'onCheckboxChange', item ]
} );
} );
ve.ui.MWTransclusionOutlineParameterSelectWidget.super.prototype.addItems.call( this, items, index );
this.setTabIndex( this.isEmpty() ? -1 : 0 );
return this;
};
/**
* @return {ve.ui.MWTransclusionOutlineParameterWidget|null}
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.findFirstSelectedItem = function () {
var firstSelected;
this.items.some( function ( item ) {
if ( !firstSelected && item.isSelected() ) {
firstSelected = item;
return true;
}
return false;
} );
return firstSelected;
};
/**
* @param {string|null} [paramName] Parameter name to set, e.g. "param1". Omit to remove setting.
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.setActiveParameter = function ( paramName ) {
// Note: We know unnamed parameter placeholders never have an item here
var newItem = paramName ? this.findItemFromData( paramName ) : null;
// Unhighlight when called with no parameter name
this.highlightItem( newItem );
paramName = paramName || null;
if ( this.activeParameter === paramName ) {
return;
}
var currentItem = this.activeParameter ? this.findItemFromData( this.activeParameter ) : null;
this.activeParameter = paramName;
if ( currentItem ) {
currentItem.toggleActivePageIndicator( false );
}
if ( newItem ) {
newItem.toggleActivePageIndicator( true );
}
};
/**
* @inheritDoc OO.ui.SelectWidget
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.highlightItem = function ( item ) {
if ( item ) {
item.scrollToView();
}
ve.ui.MWTransclusionOutlineParameterSelectWidget.super.prototype.highlightItem.call( this, item );
};
/**
* @param {string} paramName
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.markParameterAsUnused = function ( paramName ) {
// There is no OO.ui.SelectWidget.unselectItemByData(), we need to do this manually
var item = paramName ? this.findItemFromData( paramName ) : null;
if ( item ) {
item.setSelected( false );
paramName = paramName || null;
// An unused parameter can't be the active (set) one; it doesn't exist in the content pane
if ( this.itemSet === paramName ) {
this.itemSet = null;
item.setParameter( false );
}
}
};
/**
* @private
* @param {ve.ui.MWTransclusionOutlineParameterWidget} item
* @param {boolean} value
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.onCheckboxChange = function ( item, value ) {
// This extra check shouldn't be necessary, but better be safe than sorry
if ( item.isSelected() !== value ) {
// Note: This should have been named `toggle…` as it toggles the item's selection
this.chooseItem( item );
}
};
/**
* @inheritDoc OO.ui.SelectWidget
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.onFocus = function ( event ) {
if ( event.target === this.$element[ 0 ] && !this.findHighlightedItem() ) {
// When tabbing into the selection list, highlight the first parameter.
this.highlightItem( this.items[ 0 ] );
}
// Don't call the parent. It makes assumptions what should be done here.
};
/**
* @inheritDoc OO.ui.SelectWidget
* @param {jQuery.Event} e
* @fires choose
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.onMouseDown = function ( e ) {
if ( e.which === OO.ui.MouseButtons.LEFT ) {
var item = this.findTargetItem( e );
// Same as pressing enter, see below.
if ( item && item.isSelected() ) {
this.emit( 'choose', item, item.isSelected() );
// Don't call the parent, i.e. can't click to unselect the item
return false;
}
}
ve.ui.MWTransclusionOutlineParameterSelectWidget.super.prototype.onMouseDown.call( this, e );
};
/**
* @inheritDoc OO.ui.SelectWidget
* @param {KeyboardEvent} e
* @fires choose
* @fires templateParameterSpaceDown
*/
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.onDocumentKeyDown = function ( e ) {
var item;
switch ( e.keyCode ) {
case OO.ui.Keys.HOME:
item = this.items[ 0 ];
if ( item ) {
this.highlightItem( item );
}
break;
case OO.ui.Keys.END:
item = this.items[ this.items.length - 1 ];
if ( item ) {
this.highlightItem( item );
}
break;
case OO.ui.Keys.SPACE:
item = this.findHighlightedItem();
if ( item ) {
// Warning, this intentionally doesn't call .chooseItem() because we don't want this
// to fire a "choose" event!
if ( item.isSelected() ) {
this.unselectItem( item );
} else {
this.selectItem( item );
}
this.emit( 'templateParameterSpaceDown', item, item.isSelected() );
}
e.preventDefault();
break;
case OO.ui.Keys.ENTER:
item = this.findHighlightedItem();
// Same as clicking with the mouse, see above.
if ( item && item.isSelected() ) {
this.emit( 'choose', item, item.isSelected() );
e.preventDefault();
// Don't call the parent, i.e. can't use enter to unselect the item
return false;
}
break;
}
ve.ui.MWTransclusionOutlineParameterSelectWidget.super.prototype.onDocumentKeyDown.call( this, e );
};
ve.ui.MWTransclusionOutlineParameterSelectWidget.prototype.onBlur = function () {
this.highlightItem();
this.unbindDocumentKeyDownListener();
};