| 104 | * Create and show the edit icon for this element. |
| 105 | * |
| 106 | * Specifically: |
| 107 | * - create icon element |
| 108 | * - add icon to DOM |
| 109 | * - position icon relative to target |
| 110 | * - add click handler to icon |
| 111 | * - monitor DOM for changes and reposition icon when changed |
| 112 | * - monitor DOM for sidebar collapse and hide/show icon |
| 113 | */ |
| 114 | showEditIconForElement: function( element ) { |
| 115 | var partial = this; |
| 116 | var $icon = this.createEditIcon(); |
| 117 | $( window.document.body ).append( $icon ); |
| 118 | $icon.on( 'click', partial.showControl.bind( this ) ); |
| 119 | this.positionIcon( element, $icon ); |
| 120 | this.repositionIconWhenDomChanges( element, $icon ); |
| 121 | }, |
| 122 | |
| 123 | positionIcon: function( element, $icon ) { |
| 124 | var css = this.getCalculatedCssForIcon( $( element ) ); |
| 125 | $icon.css( css ); |
| 126 | }, |
| 127 | |
| 128 | repositionIconWhenDomChanges: function( element, $icon ) { |
| 129 | var reposition = _.debounce( this.positionIcon.bind( this, element, $icon ), 350 ); |
| 130 | // Reposition after page changes (if browser supports it) |
| 131 | var page = window.document.querySelector( '#page' ); |
| 132 | if ( page && 'undefined' !== typeof MutationObserver ) { |
| 133 | var observer = new MutationObserver( reposition ); |
| 134 | observer.observe( page, { attributes: true, childList: true, characterData: true } ); |
| 135 | } |
| 136 | // Reposition when window is resized |
| 137 | $( window ).resize( reposition ); |
| 138 | // Reposition when any customizer setting changes |
| 139 | api.bind( 'change', reposition ); |
| 140 | var $document = $( window.document ); |
| 141 | // Reposition after scroll in case there are fixed position elements |
| 142 | $document.on( 'scroll', reposition ); |
| 143 | // Reposition after page click (eg: hamburger menus) |
| 144 | $document.on( 'click', reposition ); |
| 145 | // TODO: reposition in other cases as well |
| 146 | }, |
| 147 | |
| 148 | getCalculatedCssForIcon: function( $target ) { |
| 149 | var hiddenIconPos = ( 'rtl' === window.document.dir ) ? { right: -1000, left: 'auto' } : { left: -1000, right: 'auto' }; |
| 150 | if ( ! $target.is( ':visible' ) ) { |
| 151 | return hiddenIconPos; |
| 152 | } |
| 153 | var offset = $target.offset(); |
| 154 | var top = offset.top; |
| 155 | var left = offset.left; |
| 156 | if ( top < 0 ) { |
| 157 | return hiddenIconPos; |
| 158 | } |
| 159 | return this.adjustCoordinates( { top: top, left: left, right: 'auto' } ); |
| 160 | }, |
| 161 | |
| 162 | adjustCoordinates: function( coords ) { |
| 163 | var minWidth = 35; |
| 164 | // Try to avoid overlapping hamburger menus |
| 165 | var maxWidth = window.innerWidth - 110; |
| 166 | if ( coords.left < minWidth ) { |
| 167 | coords.left = minWidth; |
| 168 | } |
| 169 | if ( coords.left >= maxWidth ) { |
| 170 | coords.left = maxWidth; |
| 171 | } |
| 172 | return coords; |
| 173 | }, |
| 174 | |
| 175 | getIconClassName: function() { |
| 176 | var partial = this; |
| 177 | // TODO: simplify the id string to avoid brackets, etc |
| 178 | return 'customize-partial-icon-' + partial.id; |
| 179 | }, |
| 180 | |
| 181 | createEditIcon: function() { |
| 182 | // TODO: get translated text for title/hover text |
| 183 | var editIconSource = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="0" fill="none" width="24" height="24"/><g><path d="M13 6l5 5-9.507 9.507c-.686-.686-.69-1.794-.012-2.485l-.002-.003c-.69.676-1.8.673-2.485-.013-.677-.677-.686-1.762-.036-2.455l-.008-.008c-.694.65-1.78.64-2.456-.036L13 6zm7.586-.414l-2.172-2.172c-.78-.78-2.047-.78-2.828 0L14 5l5 5 1.586-1.586c.78-.78.78-2.047 0-2.828zM3 18v3h3c0-1.657-1.343-3-3-3z"/></g></svg>'; |
| 184 | return $( '<div class="customize-partial-icon ' + this.getIconClassName() + '">' + editIconSource + '</div>' ); |
| 185 | }, |
| 186 | |
| 187 | /** |