114
114
} ) ;
115
115
116
116
} ) ( jQuery ) ;
117
+
118
+ /**
119
+ * Create language buttons to show multilingual metadata
120
+ * [data-pkp-switcher-data]: Publication data for the switchers to control
121
+ * [data-pkp-switcher]: Switchers' containers
122
+ */
123
+ ( ( ) => {
124
+ function createSwitcher ( switcherContainer , data , localeOrder , localeNames , ariaLabels ) {
125
+ // Get all locales for the switcher from the data
126
+ const locales = Object . keys ( Object . assign ( { } , ...Object . values ( data ) ) ) ;
127
+ // The initially selected locale
128
+ let selectedLocale = null ;
129
+ // Create and sort to alphabetical order
130
+ const buttons = localeOrder
131
+ . map ( ( locale ) => {
132
+ if ( locales . indexOf ( locale ) === - 1 ) {
133
+ return null ;
134
+ }
135
+ if ( ! selectedLocale ) {
136
+ selectedLocale = locale ;
137
+ }
138
+
139
+ const isSelectedLocale = locale === selectedLocale ;
140
+ const button = document . createElement ( 'button' ) ;
141
+
142
+ button . type = 'button' ;
143
+ button . classList . add ( 'pkpBadge' , 'pkpBadge--button' ) ;
144
+ button . value = locale ;
145
+ button . tabIndex = isSelectedLocale ? '0' : '-1' ; // For safari losing button focus
146
+ if ( ! isSelectedLocale ) {
147
+ button . classList . add ( 'pkp_screen_reader' ) ;
148
+ }
149
+
150
+ // Text content
151
+ /// SR
152
+ const srText = document . createElement ( 'span' ) ;
153
+ srText . classList . add ( 'pkp_screen_reader' ) ;
154
+ srText . textContent = ariaLabels [ locale ] ;
155
+ button . appendChild ( srText ) ;
156
+ // Visual
157
+ const text = document . createElement ( 'span' ) ;
158
+ text . ariaHidden = 'true' ;
159
+ text . textContent = localeNames [ locale ] ;
160
+ button . appendChild ( text ) ;
161
+
162
+ return button ;
163
+ } )
164
+ . filter ( ( btn ) => btn )
165
+ . sort ( ( a , b ) => a . value . localeCompare ( b . value ) ) ;
166
+
167
+ // If only one button, set it disabled
168
+ if ( buttons . length === 1 ) {
169
+ buttons [ 0 ] . ariaDisabled = 'true' ;
170
+ }
171
+
172
+ buttons . forEach ( ( btn ) => {
173
+ const option = document . createElement ( 'li' ) ;
174
+ option . role = 'option' ;
175
+ option . ariaSelected = `${ btn . value === selectedLocale } ` ;
176
+ option . appendChild ( btn ) ;
177
+ // Listbox: Ul element
178
+ switcherContainer . firstElementChild . appendChild ( option ) ;
179
+ } ) ;
180
+
181
+ return buttons ;
182
+ }
183
+
184
+ /**
185
+ * Sync data in elements to match the selected locale
186
+ */
187
+ function syncDataElContents ( locale , propsData , langAttrs ) {
188
+ for ( prop in propsData . data ) {
189
+ propsData . dataEls [ prop ] . lang = langAttrs [ locale ] ;
190
+ propsData . dataEls [ prop ] . innerHTML = propsData . data [ prop ] [ locale ] ?? '' ;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Toggle visibility of the buttons
196
+ * pkp_screen_reader: button visibility hidden
197
+ */
198
+ function setVisibility ( buttons , currentSelected , showList ) {
199
+ buttons . forEach ( ( btn ) => {
200
+ if ( showList ) {
201
+ btn . classList . remove ( 'pkp_screen_reader' ) ;
202
+ } else if ( btn !== currentSelected . btn ) {
203
+ btn . classList . add ( 'pkp_screen_reader' ) ;
204
+ }
205
+ } ) ;
206
+ }
207
+
208
+ function setSwitcher ( propsData , switcherContainer , localeOrder , localeNames , langAttrs , ariaLabels ) {
209
+ // Create buttons and append them to the switcher container
210
+ const buttons = createSwitcher ( switcherContainer , propsData . data , localeOrder , localeNames , ariaLabels ) ;
211
+ const currentSelected = { btn : switcherContainer . querySelector ( '[tabindex="0"]' ) } ;
212
+ const focused = { btn : currentSelected . btn } ;
213
+
214
+ // Sync contents in data elements to match the selected locale (currentSelected.btn.value)
215
+ syncDataElContents ( currentSelected . btn . value , propsData , langAttrs ) ;
216
+
217
+ // Do not add listeners if just one button, it is disabled
218
+ if ( buttons . length < 2 ) {
219
+ return ;
220
+ }
221
+
222
+ // New button switches language and syncs data contents. Same button hides buttons.
223
+ switcherContainer . addEventListener ( 'click' , ( evt ) => {
224
+ // Choices are button, span, and li
225
+ const newSelectedBtn = evt . target . tagName === 'BUTTON' ? evt . target : ( evt . target . tagName === 'SPAN' ? evt . target . parentElement : evt . target . firstElementChild ) ;
226
+ if ( newSelectedBtn ?. type === 'button' ) {
227
+ // Set visibility
228
+ setVisibility ( buttons , currentSelected , newSelectedBtn !== currentSelected . btn ? true : buttons . some ( b => b . classList . contains ( 'pkp_screen_reader' ) ) ) ;
229
+ if ( newSelectedBtn !== currentSelected . btn ) {
230
+ // Sync contents
231
+ syncDataElContents ( newSelectedBtn . value , propsData , langAttrs ) ;
232
+ // Li: Aria
233
+ currentSelected . btn . parentElement . ariaSelected = 'false' ;
234
+ newSelectedBtn . parentElement . ariaSelected = 'true' ;
235
+ // Button: Tab index
236
+ currentSelected . btn . tabIndex = '-1' ;
237
+ newSelectedBtn . tabIndex = '0' ;
238
+ // Update current and focused button
239
+ focused . btn = currentSelected . btn = newSelectedBtn ;
240
+ focused . btn . focus ( ) ;
241
+ }
242
+ }
243
+ } ) ;
244
+
245
+ // Hide buttons when focus out
246
+ switcherContainer . addEventListener ( 'focusout' , ( evt ) => {
247
+ if (
248
+ evt . relatedTarget ?. parentElement . parentElement !== switcherContainer // li
249
+ && evt . relatedTarget ?. parentElement . parentElement . parentElement !== switcherContainer // button
250
+ && evt . relatedTarget ?. parentElement . parentElement . parentElement . parentElement !== switcherContainer // span
251
+ ) {
252
+ setVisibility ( buttons , currentSelected , false ) ;
253
+ }
254
+ } ) ;
255
+
256
+ // Arrow keys left and right cycles button focus when buttons visible. Set focused button.
257
+ switcherContainer . addEventListener ( "keydown" , ( evt ) => {
258
+ if ( evt . target . type === 'button' && ( evt . key === "ArrowRight" || evt . key === "ArrowLeft" ) ) {
259
+ focused . btn = ( evt . key === "ArrowRight" )
260
+ ? ( focused . btn . parentElement . nextElementSibling ?. firstElementChild ?? buttons [ 0 ] )
261
+ : ( focused . btn . parentElement . previousElementSibling ?. firstElementChild ?? buttons [ buttons . length - 1 ] ) ;
262
+ focused . btn . focus ( ) ;
263
+ }
264
+ } ) ;
265
+ }
266
+
267
+ /**
268
+ * Set all multilingual data and elements for the switchers
269
+ */
270
+ function setSwitchersData ( dataEls , pubLocaleData ) {
271
+ const propsData = { } ;
272
+ dataEls . forEach ( ( dataEl ) => {
273
+ const propName = dataEl . getAttribute ( 'data-pkp-switcher-data' ) ;
274
+ const switcherName = pubLocaleData [ propName ] . switcher ;
275
+ if ( ! propsData [ switcherName ] ) {
276
+ propsData [ switcherName ] = { data : [ ] , dataEls : [ ] } ;
277
+ }
278
+ propsData [ switcherName ] . data [ propName ] = pubLocaleData [ propName ] . data ;
279
+ propsData [ switcherName ] . dataEls [ propName ] = dataEl ;
280
+ } ) ;
281
+ return propsData ;
282
+ }
283
+
284
+ ( ( ) => {
285
+ const switcherContainers = document . querySelectorAll ( '[data-pkp-switcher]' ) ;
286
+
287
+ if ( ! switcherContainers . length ) return ;
288
+
289
+ const pubLocaleData = JSON . parse ( pubLocaleDataJson ) ;
290
+ const switchersDataEls = document . querySelectorAll ( '[data-pkp-switcher-data]' ) ;
291
+ const switchersData = setSwitchersData ( switchersDataEls , pubLocaleData ) ;
292
+ // Create and set switchers, and sync data on the page
293
+ switcherContainers . forEach ( ( switcherContainer ) => {
294
+ const switcherName = switcherContainer . getAttribute ( 'data-pkp-switcher' ) ;
295
+ if ( switchersData [ switcherName ] ) {
296
+ setSwitcher ( switchersData [ switcherName ] , switcherContainer , pubLocaleData . localeOrder , pubLocaleData . localeNames , pubLocaleData . langAttrs , pubLocaleData . ariaLabels ) ;
297
+ }
298
+ } ) ;
299
+ } ) ( ) ;
300
+ } ) ( ) ;
0 commit comments