diff --git a/.jsduck/categories.json b/.jsduck/categories.json index 351d2fc5c4..55d7a82dc5 100644 --- a/.jsduck/categories.json +++ b/.jsduck/categories.json @@ -19,6 +19,7 @@ { "name": "User Interface", "classes": [ + "ve.ui.MWAriaDescribe", "ve.ui.MW*Page", "ve.ui.MW*Window" ] diff --git a/.jsduck/mw-categories.json b/.jsduck/mw-categories.json index 881ce354a1..bd7e76ab7d 100644 --- a/.jsduck/mw-categories.json +++ b/.jsduck/mw-categories.json @@ -19,6 +19,7 @@ { "name": "User Interface", "classes": [ + "ve.ui.MWAriaDescribe", "ve.ui.MW*Page", "ve.ui.MW*Window" ] diff --git a/extension.json b/extension.json index 2a6b2a81f6..c555dc77af 100644 --- a/extension.json +++ b/extension.json @@ -2148,6 +2148,7 @@ "modules/ve-mw/dm/models/ve.dm.MWTemplateModel.js", "modules/ve-mw/dm/models/ve.dm.MWTemplatePlaceholderModel.js", "modules/ve-mw/dm/models/ve.dm.MWParameterModel.js", + "modules/ve-mw/ui/ve.ui.MWAriaDescribe.js", "modules/ve-mw/ui/widgets/ve.ui.MWDismissibleMessageWidget.js", "modules/ve-mw/ui/widgets/ve.ui.MWParameterCheckboxInputWidget.js", "modules/ve-mw/ui/widgets/ve.ui.MWParameterSearchWidget.js", @@ -2246,6 +2247,7 @@ "visualeditor-dialog-transclusion-param-example-long", "visualeditor-dialog-transclusion-param-info", "visualeditor-dialog-transclusion-param-info-missing", + "visualeditor-dialog-transclusion-param-selection-aria-description", "visualeditor-dialog-transclusion-param-selection-aria-label", "visualeditor-dialog-transclusion-param-undocumented", "visualeditor-dialog-transclusion-placeholder", diff --git a/i18n/ve-mw/en.json b/i18n/ve-mw/en.json index 946eab5508..4bbb3697a3 100644 --- a/i18n/ve-mw/en.json +++ b/i18n/ve-mw/en.json @@ -213,6 +213,7 @@ "visualeditor-dialog-transclusion-param-example-long": "Example: $1", "visualeditor-dialog-transclusion-param-info": "Field description", "visualeditor-dialog-transclusion-param-info-missing": "No field description available", + "visualeditor-dialog-transclusion-param-selection-aria-description": "Press Space to add or remove parameters. Press Enter to add a parameter and immediately edit its value. When a parameter is already selected, press Enter to edit the value.", "visualeditor-dialog-transclusion-param-selection-aria-label": "Parameters in $1", "visualeditor-dialog-transclusion-param-undocumented": "(Undocumented parameter)", "visualeditor-dialog-transclusion-placeholder": "Add a template", diff --git a/i18n/ve-mw/qqq.json b/i18n/ve-mw/qqq.json index 9b85bac342..36776cb83f 100644 --- a/i18n/ve-mw/qqq.json +++ b/i18n/ve-mw/qqq.json @@ -231,6 +231,7 @@ "visualeditor-dialog-transclusion-param-example-long": "Label for parameter's example value in the template dialog. If feasible without abbreviation. $1 - Parameter's example value.\n{{Identical|Example}}", "visualeditor-dialog-transclusion-param-info": "Title of button that hides and shows parameter descriptions", "visualeditor-dialog-transclusion-param-info-missing": "Title of button that hides and shows parameter descriptions when no description is available", + "visualeditor-dialog-transclusion-param-selection-aria-description": "ARIA description for screen readers on the template parameter selection menu for the keyboard controls.", "visualeditor-dialog-transclusion-param-selection-aria-label": "ARIA label for screen readers on template parameter selection menu.\n\nParameters:\n* $1 - The title of the related template.", "visualeditor-dialog-transclusion-param-undocumented": "Label shown next to a parameter's name indicating that the parameter is undocumented.", "visualeditor-dialog-transclusion-placeholder": "Label for section with options for adding a new template to a multi part transclusion.\n{{Identical|Add template}}", diff --git a/modules/ve-mw/ui/ve.ui.MWAriaDescribe.js b/modules/ve-mw/ui/ve.ui.MWAriaDescribe.js new file mode 100644 index 0000000000..7cd76f7f46 --- /dev/null +++ b/modules/ve-mw/ui/ve.ui.MWAriaDescribe.js @@ -0,0 +1,44 @@ +/** + * Mixin for adding descriptive ARIA support to elements. + * + * @class + * @abstract + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {string} [ariaDescriptionId] + * @cfg {string} [ariaLabel] + */ +ve.ui.MWAriaDescribe = function VeUiMWAriaDescribe( config ) { + if ( config.ariaDescriptionId ) { + this.setAriaDescriptionId( config.ariaDescriptionId ); + } + + if ( config.ariaLabel ) { + this.setAriaLabel( config.ariaLabel ); + } +}; + +/* Setup */ + +OO.initClass( ve.ui.MWAriaDescribe ); + +/** + * @param {string} id + * @chainable + * @return {OO.ui.Element} The element, for chaining + */ +ve.ui.MWAriaDescribe.prototype.setAriaDescriptionId = function ( id ) { + this.$element.attr( 'aria-describedby', id ); + return this; +}; + +/** + * @param {string} label + * @chainable + * @return {OO.ui.Element} The element, for chaining + */ +ve.ui.MWAriaDescribe.prototype.setAriaLabel = function ( label ) { + this.$element.attr( 'aria-label', label ); + return this; +}; diff --git a/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineParameterSelectWidget.js b/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineParameterSelectWidget.js index 56bb5b4a72..5c02ab81f5 100644 --- a/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineParameterSelectWidget.js +++ b/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineParameterSelectWidget.js @@ -19,6 +19,7 @@ ve.ui.MWTransclusionOutlineParameterSelectWidget = function VeUiMWTransclusionOu OO.ui.mixin.TabIndexedElement.call( this, { tabIndex: this.isEmpty() ? -1 : 0 } ); + ve.ui.MWAriaDescribe.call( this, config ); this.$element .on( { @@ -33,6 +34,7 @@ ve.ui.MWTransclusionOutlineParameterSelectWidget = function VeUiMWTransclusionOu 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 */ diff --git a/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineTemplateWidget.js b/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineTemplateWidget.js index 24f33e2c1e..7b39160a79 100644 --- a/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineTemplateWidget.js +++ b/modules/ve-mw/ui/widgets/ve.ui.MWTransclusionOutlineTemplateWidget.js @@ -52,20 +52,26 @@ ve.ui.MWTransclusionOutlineTemplateWidget = function VeUiMWTransclusionOutlineTe classes: [ 've-ui-mwTransclusionOutlineTemplateWidget-no-match' ] } ).toggle( false ); + var $parametersAriaDescription = $( '' ) + .text( ve.msg( 'visualeditor-dialog-transclusion-param-selection-aria-description' ) ) + .attr( 'id', OO.ui.generateElementId() ) + .addClass( 've-ui-mwTransclusionOutline-ariaHidden' ); + this.parameters = new ve.ui.MWTransclusionOutlineParameterSelectWidget( { - items: parameterNames.map( this.createCheckbox.bind( this ) ) + items: parameterNames.map( this.createCheckbox.bind( this ) ), + ariaLabel: ve.msg( 'visualeditor-dialog-transclusion-param-selection-aria-label', spec.getLabel() ), + ariaDescriptionId: $parametersAriaDescription.attr( 'id' ) } ) .connect( this, { choose: 'onTemplateParameterChoose', templateParameterSelectionChanged: 'onTemplateParameterSelectionChanged', change: 'onParameterWidgetListChanged' } ); - this.parameters.$element - .attr( 'aria-label', ve.msg( 'visualeditor-dialog-transclusion-param-selection-aria-label', spec.getLabel() ) ); this.$element.append( this.searchWidget.$element, this.infoWidget.$element, + $parametersAriaDescription, this.parameters.$element ); };