1/*global wc_single_product_params, PhotoSwipe, PhotoSwipeUI_Default */ 2jQuery( function( $ ) { 3 4 // wc_single_product_params is required to continue. 5 if ( typeof wc_single_product_params === 'undefined' ) { 6 return false; 7 } 8 9 $( 'body' ) 10 // Tabs 11 .on( 'init', '.wc-tabs-wrapper, .woocommerce-tabs', function() { 12 $( this ).find( '.wc-tab, .woocommerce-tabs .panel:not(.panel .panel)' ).hide(); 13 14 var hash = window.location.hash; 15 var url = window.location.href; 16 var $tabs = $( this ).find( '.wc-tabs, ul.tabs' ).first(); 17 18 if ( hash.toLowerCase().indexOf( 'comment-' ) >= 0 || hash === '#reviews' || hash === '#tab-reviews' ) { 19 $tabs.find( 'li.reviews_tab a' ).trigger( 'click' ); 20 } else if ( url.indexOf( 'comment-page-' ) > 0 || url.indexOf( 'cpage=' ) > 0 ) { 21 $tabs.find( 'li.reviews_tab a' ).trigger( 'click' ); 22 } else if ( hash === '#tab-additional_information' ) { 23 $tabs.find( 'li.additional_information_tab a' ).trigger( 'click' ); 24 } else { 25 $tabs.find( 'li:first a' ).trigger( 'click' ); 26 } 27 } ) 28 .on( 'click', '.wc-tabs li a, ul.tabs li a', function( e ) { 29 e.preventDefault(); 30 var $tab = $( this ); 31 var $tabs_wrapper = $tab.closest( '.wc-tabs-wrapper, .woocommerce-tabs' ); 32 var $tabs = $tabs_wrapper.find( '.wc-tabs, ul.tabs' ); 33 34 $tabs.find( 'li' ).removeClass( 'active' ); 35 $tabs 36 .find( 'a[role="tab"]' ) 37 .attr( 'aria-selected', 'false' ) 38 .attr( 'tabindex', '-1' ); 39 $tabs_wrapper.find( '.wc-tab, .panel:not(.panel .panel)' ).hide(); 40 41 $tab.closest( 'li' ).addClass( 'active' ); 42 $tab 43 .attr( 'aria-selected', 'true' ) 44 .attr( 'tabindex', '0' ); 45 $tabs_wrapper.find( '#' + $tab.attr( 'href' ).split( '#' )[1] ).show(); 46 } ) 47 .on( 'keydown', '.wc-tabs li a, ul.tabs li a', function( e ) { 48 var isRTL = document.documentElement.dir === 'rtl'; 49 var direction = e.key; 50 var next = isRTL ? 'ArrowLeft' : 'ArrowRight'; 51 var prev = isRTL ? 'ArrowRight' : 'ArrowLeft'; 52 var down = 'ArrowDown'; 53 var up = 'ArrowUp'; 54 var home = 'Home'; 55 var end = 'End'; 56 57 if ( ! [ next, prev, down, up, end, home ].includes( direction ) ) { 58 return; 59 } 60 61 var $tab = $( this ); 62 var $tabs_wrapper = $tab.closest( '.wc-tabs-wrapper, .woocommerce-tabs' ); 63 var $tabsList = $tabs_wrapper.find( '.wc-tabs, ul.tabs' ); 64 var $tabs = $tabsList.find( 'a[role="tab"]' ); 65 var endIndex = $tabs.length - 1; 66 var tabIndex = $tabs.index( $tab ); 67 var targetIndex = direction === prev || direction === up ? tabIndex - 1 : tabIndex + 1; 68 var orientation = 'horizontal'; 69 70 /** 71 * We don't know if the tabs are going to be vertical or horizontal, 72 * so let's try to detect the orientation depending on the position of the tabs. 73 */ 74 if ( $tabs.length >= 2 ) { 75 var firstTab = $tabs[0].getBoundingClientRect(); 76 var secondTab = $tabs[1].getBoundingClientRect(); 77 78 var orientation = Math.abs( secondTab.top - firstTab.top ) > Math.abs( secondTab.left - firstTab.left ) 79 ? 'vertical' 80 : 'horizontal'; 81 } 82 83 /** 84 * If the tabs are vertical, we don't need to detect left/right keys 85 * If the tabs are horizontal, we don't need to detect up/down keys 86 */ 87 if ( 88 ( orientation === 'vertical' && ( direction === prev || direction === next ) ) || 89 ( orientation === 'horizontal' && ( direction === up || direction === down ) ) 90 ) { 91 return; 92 } 93 94 e.preventDefault(); 95 96 if ( 97 ( direction === prev && tabIndex === 0 && orientation === 'horizontal' ) || 98 ( direction === up && tabIndex === 0 && orientation === 'vertical' ) || 99 direction === end 100 ) { 101 targetIndex = endIndex; 102 } else if ( 103 ( next === direction && tabIndex === endIndex && orientation === 'horizontal' ) || 104 ( down === direction && tabIndex === endIndex && orientation === 'vertical' ) || 105 direction === home 106 ) { 107 targetIndex = 0; 108 } 109 110 $tabs.eq( targetIndex ).trigger( 'focus' ); 111 } ) 112 // Review link 113 .on( 'click', 'a.woocommerce-review-link', function() { 114 $( '.reviews_tab a' ).trigger( 'click' ); 115 return true; 116 } ) 117 // Star ratings for comments 118 .on( 'init', '#rating', function() { 119 $( this ) 120 .hide() 121 .before( 122 '<p class="stars">\ 123 <span role="group" aria-labelledby="comment-form-rating-label">\ 124 <a role="radio" tabindex="0" aria-checked="false" class="star-1" href="#">
124' + 125 wc_single_product_params.i18n_rating_options[0] + 126 '</a>\ 127 <a role="radio" tabindex="-1" aria-checked="false" class="star-2" href="#">' + 128 wc_single_product_params.i18n_rating_options[1] + 129 '</a>\ 130 <a role="radio" tabindex="-1" aria-checked="false" class="star-3" href="#">' + 131 wc_single_product_params.i18n_rating_options[2] + 132 '</a>\ 133 <a role="radio" tabindex="-1" aria-checked="false" class="star-4" href="#">' + 134 wc_single_product_params.i18n_rating_options[3] + 135 '</a>\ 136 <a role="radio" tabindex="-1" aria-checked="false" class="star-5" href="#">' + 137 wc_single_product_params.i18n_rating_options[4] + 138 '</a>\ 139 </span>\ 140 </p>' 141 ); 142 } ) 143 .on( 'click', '#respond p.stars a', function() { 144 var $star = $( this ), 145 starPos = $star.closest( 'p.stars' ).find( 'a' ).index( $star ) + 1, 146 $rating = $( this ).closest( '#respond' ).find( '#rating' ), 147 $container = $( this ).closest( '.stars' ); 148 149 $rating.val( starPos ); 150 $star.siblings( 'a' ) 151 .removeClass( 'active' ) 152 .attr( 'aria-checked', 'false' ) 153 .attr( 'tabindex', '-1' ); 154 $star 155 .addClass( 'active' ) 156 .attr( 'aria-checked', 'true' ) 157 .attr( 'tabindex', '0' ); 158 $container.addClass( 'selected' ); 159 160 return false; 161 } ) 162 .on( 'click', '#respond #submit', function() { 163 var $rating = $( this ).closest( '#respond' ).find( '#rating' ), 164 rating = $rating.val(); 165 166 if ( $rating.length > 0 && ! rating && wc_single_product_params.review_rating_required === 'yes' ) { 167 window.alert( wc_single_product_params.i18n_required_rating_text ); 168 169 return false; 170 } 171 } ) 172 /** 173 * Handle keyup events for tabs, tabs li a, and respond p.stars a. 174 * The stopPropagation is used to prevent the keyup event from being triggered on the flexslider. 175 */ 176 .on( 'keyup', '.wc-tabs li a, ul.tabs li a, #respond p.stars a', function( e ) { 177 var direction = e.key; 178 var next = [ 'ArrowRight', 'ArrowDown' ]; 179 var prev = [ 'ArrowLeft', 'ArrowUp' ]; 180 var allDirections = next.concat( prev ); 181 182 if ( ! allDirections.includes( direction ) ) { 183 return; 184 } 185 186 e.preventDefault(); 187 e.stopPropagation(); 188 189 if ( next.includes( direction ) ) { 190 $( this ).next().trigger( 'focus' ).click(); 191 192 return; 193 } 194 195 $( this ).prev().trigger( 'focus' ).click(); 196 } ); 197 198 // Init Tabs and Star Ratings 199 $( '.wc-tabs-wrapper, .woocommerce-tabs, #rating' ).trigger( 'init' ); 200 201 var productGalleryElement; 202 203 /** 204 * Product gallery class. 205 */ 206 var ProductGallery = function( $target, args ) { 207 this.$target = $target; 208 this.$images = $( '.woocommerce-product-gallery__image', $target ); 209 210 // No images? Abort. 211 if ( 0 === this.$images.length ) { 212 this.$target.css( 'opacity', 1 ); 213 return; 214 } 215 216 // Make this object available. 217 $target.data( 'product_gallery', this ); 218 219 // Pick functionality to initialize... 220 this.flexslider_enabled = 'function' === typeof $.fn.flexslider && wc_single_product_params.flexslider_enabled; 221 this.zoom_enabled = 'function' === typeof $.fn.zoom && wc_single_product_params.zoom_enabled; 222 this.photoswipe_enabled = typeof PhotoSwipe !== 'undefined' && wc_single_product_params.photoswipe_enabled; 223 224 // ...also taking args into account. 225 if ( args ) { 226 this.flexslider_enabled = false === args.flexslider_enabled ? false : this.flexslider_enabled; 227 this.zoom_enabled = false === args.zoom_enabled ? false : this.zoom_enabled; 228 this.photoswipe_enabled = false === args.photoswipe_enabled ? false : this.photoswipe_enabled; 229 } 230 231 // ...and what is in the gallery. 232 if ( 1 === this.$images.length ) { 233 this.flexslider_enabled = false; 234 } 235 236 // Bind functions to this. 237 this.initFlexslider = this.initFlexslider.bind( this ); 238 this.initZoom = this.initZoom.bind( this ); 239 this.initZoomForTarget = this.initZoomForTarget.bind( this ); 240 this.initPhotoswipe = this.initPhotoswipe.bind( this ); 241 this.initVideoThumbnailPreviews = this.initVideoThumbnailPreviews.bind( this ); 242 this.syncVideoPlayback = this.syncVideoPlayback.bind( this ); 243 this.getPhotoswipeVideoHtml = this.getPhotoswipeVideoHtml.bind( this ); 244 this.syncPhotoswipeVideoPlayback = this.syncPhotoswipeVideoPlayback.bind( this ); 245 this.onResetSlidePosition = this.onResetSlidePosition.bind( this ); 246 this.getGalleryItems = this.getGalleryItems.bind( this ); 247 this.openPhotoswipe = this.openPhotoswipe.bind( this ); 248 this.trapFocusPhotoswipe = this.trapFocusPhotoswipe.bind( this ); 249 this.handlePswpTrapFocus = this.handlePswpTrapFocus.bind( this ); 250 251 if ( this.flexslider_enabled ) { 252 this.initFlexslider( args.flexslider ); 253 $target.on( 'woocommerce_gallery_reset_slide_position', this.onResetSlidePosition ); 254 } else { 255 this.$target.css( 'opacity', 1 ); 256 this.syncVideoPlayback( 0 ); 257 } 258 259 if ( this.zoom_enabled ) { 260 this.initZoom(); 261 $target.on( 'woocommerce_gallery_init_zoom', this.initZoom ); 262 } 263 264 if ( this.photoswipe_enabled ) { 265 this.initPhotoswipe(); 266 } 267 }; 268 269 /** 270 * Initialize flexSlider. 271 */ 272 ProductGallery.prototype.initFlexslider = function( args ) { 273 var $target = this.$target, 274 gallery = this; 275 276 var options = $.extend( { 277 selector: '.woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image', 278 start: function( slider ) { 279 $target.css( 'opacity', 1 ); 280 gallery.syncVideoPlayback( slider.currentSlide ); 281 gallery.syncVideoThumbnailPreviewOpacity(); 282 }, 283 after: function( slider ) { 284 gallery.initZoomForTarget( gallery.$images.eq( slider.currentSlide ) ); 285 gallery.syncVideoPlayback( slider.currentSlide ); 286 gallery.syncVideoThumbnailPreviewOpacity(); 287 } 288 }, args ); 289 290 $target.flexslider( options ); 291 gallery.initVideoThumbnailPreviews(); 292 $target 293 .off( 'click.wcProductGalleryVideoThumbs', '.flex-control-thumbs img' ) 294 .on( 'click.wcProductGalleryVideoThumbs', '.flex-control-thumbs img', function() { 295 window.setTimeout( function() { 296 gallery.syncVideoPlayback(); 297 gallery.syncVideoThumbnailPreviewOpacity(); 298 }, 0 ); 299 } ); 300 301 // Trigger resize after main image loads to ensure correct gallery size. 302 $( '.woocommerce-product-gallery__wrapper .woocommerce-product-gallery__image:eq(0) .wp-post-image' ).one( 'load', function() { 303 var $image = $( this ); 304 305 if ( $image ) { 306 setTimeout( function() { 307 var setHeight = $image.closest( '.woocommerce-product-gallery__image' ).height(); 308 var $viewport = $image.closest( '.flex-viewport' ); 309 310 if ( setHeight && $viewport ) { 311 $viewport.height( setHeight ); 312 } 313 }, 100 ); 314 } 315 } ).each( function() { 316 if ( this.complete ) { 317 $( this ).trigger( 'load' ); 318 } 319 } ); 320 }; 321 322 /** 323 * Play only the active gallery video. 324 */ 325 ProductGallery.prototype.syncVideoPlayback = function( slideIndex ) { 326 const $activeSlide = 'number' === typeof slideIndex 327 ? this.$images.eq( slideIndex ) 328 : this.$target.find( '.flex-active-slide' ).first(); 329 330 this.$images.find( 'video.wp-post-video' ).each( function() { 331 if ( $activeSlide.has( this ).length ) {
332 const playPromise = this.play(); 333 334 if ( playPromise && playPromise.catch ) { 335 playPromise.catch( function() { 336 return undefined; 337 } ); 338 } 339 } else { 340 this.pause(); 341 } 342 } ); 343 }; 344 345 /** 346 * Show browser-rendered previews for video thumbnails without posters. 347 */ 348 ProductGallery.prototype.initVideoThumbnailPreviews = function() { 349 const $thumbs = this.$target.find( '.flex-control-thumbs img' ); 350 351 if ( ! $thumbs.length ) { 352 return; 353 } 354 355 this.$images.each( function( index, slide ) { 356 const videoSrc = $( slide ).attr( 'data-thumb-video-src' ); 357 const $thumb = $thumbs.eq( index ); 358 const $thumbItem = $thumb.closest( 'li' ); 359 360 if ( ! videoSrc || ! $thumb.length || $thumb.siblings( 'video' ).length ) { 361 return; 362 } 363 364 const video = $( '<video />', { 365 class: 'woocommerce-product-gallery__video-thumbnail-preview', 366 src: videoSrc, 367 preload: 'metadata', 368 muted: 'muted', 369 playsinline: 'playsinline', 370 'aria-hidden': 'true', 371 } ).css( { 372 position: 'absolute', 373 top: 0, 374 right: 0, 375 bottom: 0, 376 left: 0, 377 width: '100%', 378 height: '100%', 379 objectFit: 'cover', 380 opacity: 0.5, 381 pointerEvents: 'none', 382 } ); 383 384 $thumbItem 385 .addClass( 'woocommerce-product-gallery__video-thumbnail' ) 386 .css( 'position', 'relative' ); 387 $thumb 388 .addClass( 'woocommerce-product-gallery__video-thumbnail-placeholder' ) 389 .css( 'opacity', 0 ) 390 .after( video ); 391 } ); 392 393 this.syncVideoThumbnailPreviewOpacity(); 394 }; 395 396 /** 397 * Sync video thumbnail preview opacity with FlexSlider active state. 398 */ 399 ProductGallery.prototype.syncVideoThumbnailPreviewOpacity = function() { 400 this.$target 401 .find( '.flex-control-thumbs li.woocommerce-product-gallery__video-thumbnail' ) 402 .each( function() { 403 const $thumbItem = $( this ); 404 const isActive = $thumbItem.find( 'img.flex-active' ).length > 0; 405 406 $thumbItem 407 .find( '.woocommerce-product-gallery__video-thumbnail-preview' ) 408 .css( 'opacity', isActive ? 1 : 0.5 ); 409 } ); 410 }; 411 412 /** 413 * Init zoom. 414 */ 415 ProductGallery.prototype.initZoom = function() { 416 if (document.readyState === 'complete') { 417 this.initZoomForTarget(this.$images.first()); 418 } else { 419 $(window).on('load', () => { 420 this.initZoomForTarget(this.$images.first()); 421 }); 422 } 423 }; 424 425 /** 426 * Init zoom. 427 */ 428 ProductGallery.prototype.initZoomForTarget = function( zoomTarget ) { 429 if ( ! this.zoom_enabled ) { 430 return false; 431 } 432 433 var galleryWidth = this.$target.width(), 434 zoomEnabled = false; 435 436 $( zoomTarget ).each( function( index, target ) { 437 var image = $( target ).find( 'img' ); 438 439 if ( image.data( 'large_image_width' ) > galleryWidth ) { 440 zoomEnabled = true; 441 return false; 442 } 443 } ); 444 445 // But only zoom if the img is larger than its container. 446 if ( zoomEnabled ) { 447 var zoom_options = $.extend( { 448 touch: false, 449 callback: function() { 450 var zoomImg = this; 451 452 setTimeout( function() { 453 zoomImg.removeAttribute( 'role' ); 454 zoomImg.setAttribute( 'alt', '' ); 455 zoomImg.setAttribute( 'aria-hidden', 'true' ); 456 }, 100 ); 457 } 458 }, wc_single_product_params.zoom_options ); 459 460 if ( 'ontouchstart' in document.documentElement ) { 461 zoom_options.on = 'click'; 462 } 463 464 zoomTarget.trigger( 'zoom.destroy' ); 465 zoomTarget.zoom( zoom_options ); 466 467 setTimeout( function() { 468 if ( zoomTarget.find(':hover').length ) { 469 zoomTarget.trigger( 'mouseover' ); 470 } 471 }, 100 ); 472 } 473 }; 474 475 /** 476 * Init PhotoSwipe. 477 */ 478 ProductGallery.prototype.initPhotoswipe = function() { 479 if ( this.zoom_enabled && this.$images.length > 0 ) { 480 this.$target.prepend( 481 '<a href="#" role="button" class="woocommerce-product-gallery__trigger" aria-haspopup="dialog" ' + 482 'aria-controls="photoswipe-fullscreen-dialog" aria-label="' + 483 wc_single_product_params.i18n_product_gallery_trigger_text + '">' + 484 '<span aria-hidden="true">ð</span>' + 485 '</a>' 486 ); 487 this.$target.on( 'click', '.woocommerce-product-gallery__trigger', this.openPhotoswipe ); 488 this.$target.on( 'keydown', '.woocommerce-product-gallery__trigger', ( e ) => { 489 if ( e.key === ' ' ) { 490 this.openPhotoswipe( e ); 491 } 492 } ); 493 this.$target.on( 'click', '.woocommerce-product-gallery__image a', this.openPhotoswipe ); 494 } else { 495 this.$target.on( 'click', '.woocommerce-product-gallery__image a', this.openPhotoswipe ); 496 } 497 }; 498 499 /** 500 * Reset slide position to 0. 501 */ 502 ProductGallery.prototype.onResetSlidePosition = function() { 503 this.$target.flexslider( 0 ); 504 }; 505 506 /** 507 * Get product gallery media items. 508 */ 509 ProductGallery.prototype.getGalleryItems = function() { 510 const $slides = this.$images; 511 const items = []; 512 const gallery = this; 513 514 if ( $slides.length > 0 ) { 515 $slides.each( function( i, el ) { 516 const media = $( el )
517 .find( 'img[data-large_image], video[data-large_image]' ) 518 .first(); 519 520 if ( media.length ) { 521 const large_image_src = media.attr( 'data-large_image' ); 522 const large_image_w = media.attr( 'data-large_image_width' ); 523 const large_image_h = media.attr( 'data-large_image_height' ); 524 const alt = media.attr( 'alt' ) || media.attr( 'aria-label' ); 525 const title = media.attr( 'data-caption' ) 526 ? media.attr( 'data-caption' ) 527 : media.attr( 'title' ); 528 let item; 529 530 if ( media.is( 'video' ) ) { 531 item = { 532 html: gallery.getPhotoswipeVideoHtml( media ), 533 w: large_image_w, 534 h: large_image_h, 535 title, 536 video: true, 537 }; 538 } else { 539 item = { 540 alt, 541 src: large_image_src, 542 w: large_image_w, 543 h: large_image_h, 544 title, 545 }; 546 } 547 548 items.push( item ); 549 } 550 } ); 551 } 552 553 return items; 554 }; 555 556 /** 557 * Get PhotoSwipe video slide HTML. 558 */ 559 ProductGallery.prototype.getPhotoswipeVideoHtml = function( media ) { 560 const video = media.get( 0 ); 561 const src = video.currentSrc || media.attr( 'src' ); 562 const $video = $( '<video />
562', { 563 class: 'woocommerce-product-gallery__photoswipe-video', 564 src, 565 preload: 'metadata', 566 muted: 'muted', 567 loop: 'loop', 568 playsinline: 'playsinline', 569 style: 'display:block;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;', 570 'aria-label': media.attr( 'aria-label' ) || '', 571 } ); 572 const poster = media.attr( 'poster' ); 573 574 if ( poster ) { 575 $video.attr( 'poster', poster ); 576 } 577 578 return $( '<div />', { 579 class: 'woocommerce-product-gallery__photoswipe-video-wrapper', 580 style: 'display:flex;align-items:center;justify-content:center;width:100%;height:100%;', 581 } ) 582 .append( $video ) 583 .prop( 'outerHTML' ); 584 }; 585 586 /** 587 * Play only the current PhotoSwipe video. 588 */ 589 ProductGallery.prototype.syncPhotoswipeVideoPlayback = function( photoswipe ) { 590 const activeContainer = 591 photoswipe.currItem && photoswipe.currItem.container; 592 593 $( photoswipe.template ) 594 .find( 'video.woocommerce-product-gallery__photoswipe-video' ) 595 .each( function() { 596 if ( activeContainer && activeContainer.contains( this ) ) { 597 const playPromise = this.play(); 598 599 if ( playPromise && playPromise.catch ) { 600 playPromise.catch( function() { 601 return undefined; 602 } ); 603 } 604 } else { 605 this.pause(); 606 } 607 } ); 608 }; 609 610 /** 611 * Open photoswipe modal. 612 */ 613 ProductGallery.prototype.openPhotoswipe = function( e ) { 614 e.preventDefault(); 615 616 var pswpElement = $( '.pswp' )[0], 617 items = this.getGalleryItems(), 618 eventTarget = $( e.target ), 619 currentTarget = e.currentTarget, 620 flexslider = this.flexslider_enabled ? this.$target.data( 'flexslider' ) : false, 621 self = this, 622 clicked, 623 index; 624 625 if ( 0 < eventTarget.closest( '.woocommerce-product-gallery__trigger' ).length ) { 626 clicked = this.$target.find( '.flex-active-slide' ); 627 } else { 628 clicked = eventTarget.closest( '.woocommerce-product-gallery__image' ); 629 } 630 631 index = $( clicked ).index(); 632 633 if ( flexslider && 'number' === typeof flexslider.currentSlide ) { 634 index = flexslider.currentSlide; 635 } 636 637 var options = $.extend( { 638 index: index, 639 addCaptionHTMLFn: function( item, captionEl ) { 640 if ( ! item.title ) { 641 captionEl.children[0].textContent = ''; 642 return false; 643 } 644 captionEl.children[0].textContent = item.title; 645 return true; 646 }, 647 timeToIdle: 0, // Ensure the gallery controls are always visible to avoid keyboard navigation issues. 648 }, wc_single_product_params.photoswipe_options ); 649 650 // Initializes and opens PhotoSwipe. 651 var photoswipe = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options ); 652 653 photoswipe.listen( 'afterInit', function() { 654 self.trapFocusPhotoswipe( true ); 655 self.syncPhotoswipeVideoPlayback( photoswipe ); 656 }); 657 658 photoswipe.listen( 'afterChange', function() { 659 self.syncPhotoswipeVideoPlayback( photoswipe ); 660 }); 661 662 photoswipe.listen( 'close', function() { 663 $( photoswipe.template ) 664 .find( 'video.woocommerce-product-gallery__photoswipe-video' ) 665 .each( function() { 666 this.pause(); 667 } ); 668 self.trapFocusPhotoswipe( false ); 669 currentTarget.focus(); 670 }); 671 672 photoswipe.init(); 673 }; 674 675 /** 676 * Control focus in photoswipe modal. 677 * 678 * @param {boolean} trapFocus - Whether to trap focus or not. 679 */ 680 ProductGallery.prototype.trapFocusPhotoswipe = function( trapFocus ) { 681 var pswp = document.querySelector( '.pswp' ); 682 683 if ( ! pswp ) { 684 return; 685 } 686 687 if ( trapFocus ) { 688 pswp.addEventListener( 'keydown', this.handlePswpTrapFocus ); 689 } else { 690 pswp.removeEventListener( 'keydown', this.handlePswpTrapFocus ); 691 } 692 }; 693 694 /** 695 * Handle keydown event in photoswipe modal. 696 */ 697 ProductGallery.prototype.handlePswpTrapFocus = function( e ) { 698 var allFocusablesEls = e.currentTarget.querySelectorAll( 'button:not([disabled])' ); 699 var filteredFocusablesEls = Array.from( allFocusablesEls ).filter( function( btn ) { 700 return btn.style.display !== 'none' && window.getComputedStyle( btn ).display !== 'none'; 701 } ); 702 703 if ( 1 >= filteredFocusablesEls.length ) { 704 return; 705 } 706 707 var firstTabStop = filteredFocusablesEls[0]; 708 var lastTabStop = filteredFocusablesEls[filteredFocusablesEls.length - 1]; 709 710 if ( e.key === 'Tab' ) { 711 if ( e.shiftKey ) { 712 if ( document.activeElement === firstTabStop ) { 713 e.preventDefault(); 714 lastTabStop.focus(); 715 } 716 } else if ( document.activeElement === lastTabStop ) { 717 e.preventDefault(); 718 firstTabStop.focus(); 719 } 720 } 721 }; 722 723 /** 724 * Function to call wc_product_gallery on jquery selector. 725 */ 726 $.fn.wc_product_gallery = function( args ) { 727 new ProductGallery( this, args || wc_single_product_params ); 728 return this; 729 }; 730 731 /* 732 * Initialize all galleries on page. 733 */ 734 $( '.woocommerce-product-gallery' ).each( function() { 735 736 $( this ).trigger( 'wc-product-gallery-before-init', [ this, wc_single_product_params ] ); 737 738 productGalleryElement = $( this ).wc_product_gallery( wc_single_product_params ); 739 740 $( this ).trigger( 'wc-product-gallery-after-init', [ this, wc_single_product_params ] ); 741 742 } ); 743} );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.