]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - vendor/oojs/oojs-ui/php/mixins/TabIndexedElement.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / vendor / oojs / oojs-ui / php / mixins / TabIndexedElement.php
1 <?php
2
3 namespace OOUI;
4
5 /**
6  * Element supporting "sequential focus navigation" using the 'tabindex' attribute.
7  *
8  * @abstract
9  */
10 trait TabIndexedElement {
11         /**
12          * Tab index value.
13          *
14          * @var number|null
15          */
16         protected $tabIndex = null;
17
18         /**
19          * @var Element
20          */
21         protected $tabIndexed;
22
23         /**
24          * @param array $config Configuration options
25          * @param string|number|null $config['tabIndex'] Tab index value. Use 0 to use default ordering,
26          *   use -1 to prevent tab focusing, use null to suppress the `tabindex` attribute. (default: 0)
27          */
28         public function initializeTabIndexedElement( array $config = [] ) {
29                 // Properties
30                 $this->tabIndexed = isset( $config['tabIndexed'] ) ? $config['tabIndexed'] : $this;
31
32                 // Initialization
33                 $this->setTabIndex( isset( $config['tabIndex'] ) ? $config['tabIndex'] : 0 );
34
35                 $this->registerConfigCallback( function ( &$config ) {
36                         if ( $this->tabIndex !== 0 ) {
37                                 $config['tabIndex'] = $this->tabIndex;
38                         }
39                 } );
40         }
41
42         /**
43          * Set tab index value.
44          *
45          * @param string|number|null $tabIndex Tab index value or null for no tab index
46          * @return $this
47          */
48         public function setTabIndex( $tabIndex ) {
49                 $tabIndex = preg_match( '/^-?\d+$/', $tabIndex ) ? (int)$tabIndex : null;
50
51                 if ( $this->tabIndex !== $tabIndex ) {
52                         $this->tabIndex = $tabIndex;
53                         $this->updateTabIndex();
54                 }
55
56                 return $this;
57         }
58
59         /**
60          * Update the tabIndex attribute, in case of changes to tabIndex or disabled
61          * state.
62          *
63          * @return $this
64          */
65         public function updateTabIndex() {
66                 $disabled = $this->isDisabled();
67                 if ( $this->tabIndex !== null ) {
68                         $this->tabIndexed->setAttributes( [
69                                 // Do not index over disabled elements
70                                 'tabindex' => $disabled ? -1 : $this->tabIndex,
71                                 // ChromeVox and NVDA do not seem to inherit this from parent elements
72                                 'aria-disabled' => ( $disabled ? 'true' : 'false' )
73                         ] );
74                 } else {
75                         $this->tabIndexed->removeAttributes( [ 'tabindex', 'aria-disabled' ] );
76                 }
77                 return $this;
78         }
79
80         /**
81          * Get tab index value.
82          *
83          * @return number|null Tab index value
84          */
85         public function getTabIndex() {
86                 return $this->tabIndex;
87         }
88
89         /**
90          * Get an ID of a focusable element of this widget, if any, to be used for `<label for>` value.
91          *
92          * If the element already has an ID then that is returned, otherwise unique ID is
93          * generated, set on the element, and returned.
94          *
95          * @return string|null The ID of the focusable element
96          */
97         public function getInputId() {
98                 $id = $this->tabIndexed->getAttribute( 'id' );
99
100                 if ( !$this->isLabelableNode( $this->tabIndexed ) ) {
101                         return null;
102                 }
103
104                 if ( $id === null ) {
105                         $id = Tag::generateElementId();
106                         $this->tabIndexed->setAttributes( [ 'id' => $id ] );
107                 }
108
109                 return $id;
110         }
111
112         /**
113          * Whether the node is 'labelable' according to the HTML spec
114          * (i.e., whether it can be interacted with through a `<label for="…">`).
115          * See: <https://html.spec.whatwg.org/multipage/forms.html#category-label>.
116          *
117          * @param Tag $tag
118          * @return boolean
119          */
120         private function isLabelableNode( Tag $tag ) {
121                 $labelableTags = [ 'button', 'meter', 'output', 'progress', 'select', 'textarea' ];
122                 $tagName = strtolower( $tag->getTag() );
123
124                 if ( $tagName === 'input' && $tag->getAttribute( 'type' ) !== 'hidden' ) {
125                         return true;
126                 }
127                 if ( in_array( $tagName, $labelableTags, true ) ) {
128                         return true;
129                 }
130                 return false;
131         }
132 }