Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 1x 5x 5x 5x 5x 5x 5x 1x 1x 5x 5x 1x 1x 1x | /*! * VisualEditor TabIndexScope class. * * @copyright See AUTHORS.txt */ /** * TabIndex Scope container constructor * * @class * @constructor * * @param {Object} [config] Configuration options * @param {jQuery} [config.root] Initial root element to scope tabIndex within * @param {boolean} [config.skipAriaDisabled] Whether to skip elements that are just aria-disabled from the order */ ve.ui.TabIndexScope = function VeUiTabIndexScope( config ) { config = ve.extendObject( { root: false, skipAriaDisabled: true, skipAriaHidden: true }, config ); this.skipAriaDisabled = config.skipAriaDisabled; this.skipAriaHidden = config.skipAriaHidden; this.onRootKeyDownBound = this.onRootKeyDown.bind( this ); Eif ( config.root ) { this.setTabRoot( config.root ); } }; /* Setup */ OO.initClass( ve.ui.TabIndexScope ); /* Methods */ /** * Set the current root element for tabbing * * @param {jQuery} $root root element to scope tabIndex within */ ve.ui.TabIndexScope.prototype.setTabRoot = function ( $root ) { Iif ( this.$root ) { this.$root.off( 'keydown', this.onRootKeyDownBound ); } this.$root = $( $root ).on( 'keydown', this.onRootKeyDownBound ); }; /** * Build a list of elements in the current root, in tab order * * This mimics browser behavior: fetch focusable elements, sort by [tabIndex, DOM order] * * @return {HTMLElement[]} list of elements in the order they should be tabbed through */ ve.ui.TabIndexScope.prototype.getElementsInRoot = function () { const elements = this.$root.find( '*' ) .filter( ( index, element ) => { if ( element.tabIndex === -1 ) { // tabIndex -1 is focusable, but shouldn't appear to keyboard-navigation return false; } if ( this.skipAriaDisabled && element.getAttribute( 'aria-disabled' ) === 'true' ) { return false; } if ( this.skipAriaHidden && $( element ).closest( '[aria-hidden="true"]', this.$root[ 0 ] ).length ) { return false; } if ( element.isContentEditable && element.contentEditable !== 'true' ) { // Skip nodes within contentEditable nodes (but not the root contentEditable nodes), // which would be focusable if they weren't editable, e.g. links. // This matches browser behavior. return false; } return OO.ui.isFocusableElement( $( element ) ); } ) .map( ( index, element ) => ( { element: element, index: index } ) ) .get(); elements.sort( ( a, b ) => { if ( a.element.tabIndex < b.element.tabIndex ) { return -1; } if ( a.element.tabIndex > b.element.tabIndex ) { return 1; } return a.index - b.index; } ); return elements.map( ( data ) => data.element ); }; /** * Handle keydown events on elements * * @private * @param {jQuery.Event} e */ ve.ui.TabIndexScope.prototype.onRootKeyDown = function ( e ) { if ( e.which !== OO.ui.Keys.TAB ) { return; } const elements = this.getElementsInRoot(); let index = elements.indexOf( e.target ); if ( index === -1 ) { return; } index += e.shiftKey ? -1 : 1; if ( ( index < 0 || index >= elements.length ) ) { return; } e.preventDefault(); elements[ index ].focus(); }; /** * Teardown tabbable elements manager */ ve.ui.TabIndexScope.prototype.teardown = function () { this.setRoot( [] ); }; |