/**
* Elementor Editor Window Extras — robust placement left of History icon
*/
if ( ! defined( 'ELEMENTOR_MIN_MOBILE_WIDTH' ) ) {
define( 'ELEMENTOR_MIN_MOBILE_WIDTH', 420 );
}
add_action( 'elementor/editor/after_enqueue_scripts', function() {
?>
<script type="text/javascript">
(function(){
const MIN_MOBILE_WIDTH = <?php echo (int) ELEMENTOR_MIN_MOBILE_WIDTH; ?>;
const ASPECTS = [
{ label: "16:9", value: 16 / 9 },
{ label: "9:16", value: 9 / 16 },
{ label: "16:10", value: 16 / 10 },
{ label: "10:16", value: 10 / 16 },
{ label: "3:4", value: 3 / 4 },
{ label: "4:3", value: 4 / 3 },
{ label: "19.5:9", value: 19.5 / 9 },
{ label: "9:19.5", value: 9 / 19.5 },
];
let minMaxActive = localStorage.getItem('elementor-minmax-toggle') === '1';
let editing = { width: false, height: false };
/* ---------- preview helpers (unchanged) ---------- */
function getPreviewWrapper() {
const candidates = [
'#elementor-preview-responsive-wrapper',
'#elementor-preview-wrapper',
'.e-editor-preview__device',
'.elementor-editor-preview',
'.elementor-editor__preview',
'#elementor-preview-iframe',
];
for (const sel of candidates) {
const el = document.querySelector(sel);
if (el) return el;
}
const iframe = document.querySelector('iframe[name^="elementor-preview"], iframe.elementor-preview, iframe.elementor-preview-iframe');
if (iframe) return iframe.closest('div') || iframe.parentElement;
return null;
}
function getBreakpoint() {
const bodyMatch = document.body.className.match(/elementor-device-([^\s]+)/);
if (bodyMatch && bodyMatch[1]) return bodyMatch[1];
const btns = document.querySelectorAll('[data-device], button[aria-pressed]');
for (const b of btns) {
const dev = b.getAttribute('data-device') || b.getAttribute('data-device-mode') || b.dataset.device;
if (dev && b.getAttribute('aria-pressed') === 'true') return dev;
}
try {
const cfg =
(window.elementorFrontend?.config?.responsive) ||
(window.elementor?.config?.responsive);
if (cfg?.activeDevice) return cfg.activeDevice;
} catch(e){}
return 'tablet';
}
function getBreakpointInfo(name) {
const defaultBps = {
mobile: { value: 767, direction: 'max', is_enabled: true },
tablet: { value: 1024, direction: 'max', is_enabled: true },
laptop: { value: 1366, direction: 'max', is_enabled: true },
desktop: { value: 9999, direction: 'min', is_enabled: true },
widescreen: { value: 2400, direction: 'min', is_enabled: true }
};
let breakpoints = defaultBps;
try {
const cfg =
(window.elementorFrontend?.config?.responsive?.activeBreakpoints) ||
(window.elementor?.config?.responsive?.activeBreakpoints);
if (cfg) breakpoints = cfg;
} catch(e){}
const bps = Object.keys(breakpoints)
.filter(k => breakpoints[k].is_enabled !== false)
.map(k => ({ name: k, value: breakpoints[k].value, direction: breakpoints[k].direction }))
.sort((a,b) => a.value - b.value);
const bp = bps.find(b => b.name === name) || { name: 'tablet', value: 1024, direction: 'max' };
const i = bps.indexOf(bp);
let min;
if (bp.name === 'mobile') {
min = MIN_MOBILE_WIDTH;
} else {
min = (bp.direction === 'max')
? (i > 0 ? (bps[i-1].name === 'mobile' ? MIN_MOBILE_WIDTH : bps[i-1].value + 1) : MIN_MOBILE_WIDTH)
: bp.value;
}
const max = bp.direction === 'max'
? bp.value
: (bps[i+1]?.value - 1) || 9999;
return { min, max };
}
function getWidth(forceLive=false) {
const w = getPreviewWrapper(); if (!w) return 0;
const bp = getBreakpoint();
if (bp === 'desktop' || bp === 'widescreen' || forceLive)
return w.offsetWidth || parseInt(getComputedStyle(w).width,10) || 0;
const cssVar = getComputedStyle(w).getPropertyValue('--e-editor-preview-width');
return parseInt(cssVar,10) || w.offsetWidth || 0;
}
function getHeight(forceLive=false) {
const w = getPreviewWrapper(); if (!w) return 0;
const bp = getBreakpoint();
if (bp === 'desktop' || bp === 'widescreen' || forceLive)
return w.offsetHeight || parseInt(getComputedStyle(w).height,10) || 0;
const cssVar = getComputedStyle(w).getPropertyValue('--e-editor-preview-height');
return parseInt(cssVar,10) || w.offsetHeight || 0;
}
function setWidthHeight(width, height, updateDisplays = true) {
const w = getPreviewWrapper();
if (!w) return;
try {
w.style.setProperty('--e-editor-preview-width', width + 'px');
w.style.setProperty('--e-editor-preview-height', height + 'px');
w.style.width = '';
w.style.height = '';
w.dispatchEvent(new Event('resize'));
} catch(e){}
if (updateDisplays) updateAllDisplays();
}
/* ---------- UI helpers (unchanged) ---------- */
function updateAllDisplays() {
const wrapper = getPreviewWrapper();
const widthSpan = document.querySelector('#width-number');
const heightSpan = document.querySelector('#height-number');
const controlWrapper = document.querySelector('.e-size-control-wrapper');
if (!wrapper || !widthSpan || !heightSpan || !controlWrapper) return;
const bp = getBreakpoint();
const disabled = (bp === 'desktop' || bp === 'widescreen');
if (!editing.width) widthSpan.textContent = getWidth(true);
if (!editing.height) heightSpan.textContent = getHeight(true);
[widthSpan, heightSpan].forEach((span,i)=>{
span.contentEditable = !disabled;
span.style.cursor = disabled ? 'default' : 'text';
span.dataset.tooltip = disabled ? '' : `Edit ${i ? 'Height':'Width'}`;
});
controlWrapper.dataset.tooltip = disabled ? 'Disabled in Desktop Mode' : '';
updateButtonVisibility();
updateMinMaxButtonVisual();
}
function updateButtonVisibility() {
const bp = getBreakpoint();
document.querySelectorAll('.e-size-icon-button').forEach(btn => btn.style.display = 'none');
if (bp === 'desktop' || bp === 'widescreen') return;
if (bp === 'laptop') {
document.querySelector('.e-size-icon-button.minmax')?.style.setProperty('display','');
} else {
document.querySelectorAll('.e-size-icon-button').forEach(btn => btn.style.display = '');
}
}
function updateMinMaxButtonVisual() {
const btn = document.querySelector('.e-size-icon-button.minmax');
if (!btn) return;
const icon = btn.querySelector('i');
if (minMaxActive) {
btn.classList.add('active');
icon.className = 'elementor-icon eicon-shrink';
icon.style.transform = 'rotate(180deg)';
btn.dataset.tooltip = 'Toggle Min Width';
} else {
btn.classList.remove('active');
icon.className = 'elementor-icon eicon-grow';
icon.style.transform = 'rotate(0deg)';
btn.dataset.tooltip = 'Toggle Max Width';
}
}
function createSizeField(type) {
const div = document.createElement('div');
div.className = 'e-size-field';
div.innerHTML = `
<span>${type.charAt(0).toUpperCase()+type.slice(1)}:</span>
<span id="${type}-number" contenteditable="false"></span>
<span>px</span>
`;
const span = div.querySelector(`#${type}-number`);
span.onfocus = ()=>{
editing[type] = true;
setTimeout(()=>{
const sel = window.getSelection();
const r = document.createRange();
r.selectNodeContents(span);
sel.removeAllRanges(); sel.addRange(r);
},10);
};
span.onblur = ()=>{
editing[type] = false;
const v = parseInt(span.textContent,10);
if (!isNaN(v) && v>0) {
if (type === 'width') setWidthHeight(v, getHeight());
else setWidthHeight(getWidth(), v);
}
setTimeout(updateAllDisplays,80);
};
span.onkeydown = e=>{
if (e.key==="Enter"){
e.preventDefault(); span.blur();
}
if (e.key==="ArrowUp" || e.key==="ArrowDown"){
e.preventDefault();
let v = parseInt(span.textContent,10) || (type==='width'?getWidth():getHeight());
v += (e.key==="ArrowUp"?1:-1);
v = Math.max(1,v);
if (type==='width') setWidthHeight(v, getHeight());
else setWidthHeight(getWidth(), v);
span.textContent = v;
}
};
let dragStart=null, origWidth=0, origHeight=0;
span.addEventListener('mousedown', e=>{
if (e.button!==0 || span.isContentEditable===false) return;
dragStart = (type==='width'?e.clientX:e.clientY);
origWidth = getWidth(); origHeight = getHeight();
document.body.style.userSelect='none';
const move = ev=>{
let delta = (type==='width'?ev.clientX:ev.clientY) - dragStart;
let v = (type==='width'?origWidth+delta:origHeight+delta);
v = Math.max(1,v);
if (type==='width') setWidthHeight(v,getHeight(),false);
else setWidthHeight(getWidth(),v,false);
span.textContent = Math.round(v);
};
const up = ()=>{
document.removeEventListener('mousemove',move);
document.removeEventListener('mouseup',up);
document.body.style.userSelect='';
setTimeout(updateAllDisplays,80);
};
document.addEventListener('mousemove',move);
document.addEventListener('mouseup',up);
});
return div;
}
function createDropdown(aspectBtn) {
const dropdown = document.createElement('div');
dropdown.className = 'e-size-aspect-dropdown eui-1aoepuf';
dropdown.style.display = 'none';
ASPECTS.forEach(opt=>{
const btn = document.createElement('button');
btn.type='button';
const nums = opt.label.split(':').map(Number);
const isLandscape = nums[0]>nums[1];
const isMobile = ["19.5:9","9:19.5"].includes(opt.label);
const icon = isMobile ? 'eicon-device-mobile' : 'eicon-device-tablet';
btn.innerHTML = `
<span class="elementor-icon ${icon}"
style="font-size:1.18em;margin-right:0.2em;vertical-align:-2px;${isLandscape?'transform:rotate(-90deg);':''}">
</span>${opt.label}
`;
btn.dataset.value = opt.value;
btn.onclick = e=>{
e.stopPropagation();
const w = getWidth();
setWidthHeight(w, Math.max(1, Math.round(w / Number(btn.dataset.value))));
dropdown.style.display='none';
aspectBtn.classList.remove('active');
const ic = aspectBtn.querySelector('.elementor-icon');
if (ic) ic.style.transform='rotate(0deg)';
setTimeout(updateAllDisplays,50);
};
dropdown.appendChild(btn);
});
document.body.appendChild(dropdown);
function placeDropdown() {
const r = aspectBtn.getBoundingClientRect();
dropdown.style.position='fixed';
dropdown.style.left = `${r.left}px`;
dropdown.style.top = `${r.top + r.height + 8}px`;
dropdown.style.width='7rem';
dropdown.style.display='block';
dropdown.style.zIndex=99999;
}
aspectBtn.onclick = e=>{
e.stopPropagation();
const ic = aspectBtn.querySelector('.elementor-icon');
if (dropdown.style.display==='block') {
dropdown.style.display='none';
aspectBtn.classList.remove('active');
if (ic) ic.style.transform='rotate(0deg)';
} else {
placeDropdown();
aspectBtn.classList.add('active');
if (ic) ic.style.transform='rotate(180deg)';
}
};
document.addEventListener('mousedown', ev=>{
if (!dropdown.contains(ev.target) && ev.target!==aspectBtn && dropdown.style.display==='block') {
dropdown.style.display='none';
aspectBtn.classList.remove('active');
const ic = aspectBtn.querySelector('.elementor-icon');
if (ic) ic.style.transform='rotate(0deg)';
}
});
return dropdown;
}
/* ---------- Improved insertion: left of History (robust) ---------- */
function createUI() {
document.querySelectorAll('.e-size-control-wrapper').forEach(n=>n.remove());
const wrapper = document.createElement('span');
wrapper.className = 'MuiBox-root eui-0 e-size-control-wrapper';
wrapper.style.pointerEvents='auto';
// ensure it doesn't shrink or overlap
wrapper.style.flex = '0 0 auto';
wrapper.style.marginLeft = '30px';
wrapper.style.marginRight = '8px';
wrapper.appendChild(createSizeField('width'));
wrapper.appendChild(createSizeField('height'));
const toggleBtn = document.createElement('span');
toggleBtn.className = 'e-size-icon-button minmax';
toggleBtn.dataset.tooltip='Toggle Max Width';
toggleBtn.innerHTML='<i class="elementor-icon eicon-grow"></i>';
toggleBtn.onclick = ()=>{
minMaxActive = !minMaxActive;
localStorage.setItem('elementor-minmax-toggle', minMaxActive?'1':'0');
const bp = getBreakpoint();
const { min,max } = getBreakpointInfo(bp);
if (minMaxActive) setWidthHeight(max,getHeight());
else setWidthHeight(min,getHeight());
updateMinMaxButtonVisual();
};
const rotateBtn = document.createElement('span');
rotateBtn.className = 'e-size-icon-button orientation';
rotateBtn.dataset.tooltip='Swap Dimensions';
rotateBtn.innerHTML='<i class="elementor-icon eicon-redo" style="transform:rotate(90deg)"></i>';
rotateBtn.onclick = ()=>{
const w = getWidth();
const h = getHeight();
setWidthHeight(h,w);
const ic = rotateBtn.querySelector('i');
const mirrored = ic.style.transform.includes('scaleX(-1)');
ic.style.transform = mirrored
? 'rotate(90deg) scaleX(1)'
: 'rotate(-90deg) scaleX(-1)';
};
const aspectBtn = document.createElement('span');
aspectBtn.className = 'e-size-icon-button aspect e-size-breakpoint-show';
aspectBtn.dataset.tooltip = 'Aspect Ratios';
aspectBtn.innerHTML = '<i class="elementor-icon eicon-frame-expand"></i>';
aspectBtn.style.fontSize='1rem';
wrapper.appendChild(toggleBtn);
wrapper.appendChild(rotateBtn);
wrapper.appendChild(aspectBtn);
let placed = false;
// 1) Prefer new header container
const header = document.querySelector('.e-editor-header, .elementor-editor-header, .elementor-top-panel, .elementor-editor-top-bar');
if (header) {
// try multiple history selectors
const historySelectors = [
'button[data-action="open-history"]',
'button[aria-label="History"]',
'button[title="History"]',
'button[data-tooltip*="History"]',
'.elementor-history-button',
'.e-history__button'
];
let historyBtn = null;
for (const sel of historySelectors) {
historyBtn = header.querySelector(sel);
if (historyBtn) break;
}
// If found, insert immediately before it (even if parent differs)
if (historyBtn) {
try {
historyBtn.parentElement.insertBefore(wrapper, historyBtn);
placed = true;
} catch(e){}
} else {
// If no history button found, try to find actions container (right/left areas)
const actionsCandidates = [
'.e-editor-header__actions',
'.elementor-editor-top-bar__right',
'.elementor-top-panel__right',
'.elementor-editor-top-controls__right',
'.elementor-top-panel'
];
for (const a of actionsCandidates) {
const actionsEl = header.querySelector(a);
if (actionsEl) {
actionsEl.appendChild(wrapper);
placed = true;
break;
}
}
}
}
// 2) Fallback for older top-bar structures
if (!placed) {
const oldTop = document.querySelector('.elementor-editor-top-bar, .elementor-top-panel, .elementor-editor__top-bar');
if (oldTop) {
// try to place before a history-like element inside oldTop if present
const hb = oldTop.querySelector('button[data-action="open-history"], button[aria-label="History"], .elementor-history-button');
if (hb && hb.parentElement) {
hb.parentElement.insertBefore(wrapper, hb);
} else {
oldTop.appendChild(wrapper);
}
placed = true;
}
}
// 3) Last-resort fixed placement (non-overlapping default)
if (!placed) {
document.body.appendChild(wrapper);
wrapper.style.position='fixed';
wrapper.style.top='13px';
wrapper.style.left='250px';
wrapper.style.zIndex=99999;
}
// build dropdown and initial display
createDropdown(aspectBtn);
updateAllDisplays();
}
/* ---------- observation and init (unchanged) ---------- */
function forceMobileMinWidth() {
const bp = getBreakpoint();
if (bp==='mobile') {
const {min} = getBreakpointInfo(bp);
if (getWidth(true)!==MIN_MOBILE_WIDTH)
setWidthHeight(MIN_MOBILE_WIDTH,getHeight(true));
}
}
function observeBreakpointAndWindowResize() {
let lastBp = getBreakpoint();
const mo = new MutationObserver(()=>{
const bp = getBreakpoint();
if (bp!==lastBp) {
lastBp = bp;
const {min,max} = getBreakpointInfo(bp);
if (minMaxActive) {
requestAnimationFrame(()=>setWidthHeight(max,getHeight(true)));
} else {
setTimeout(forceMobileMinWidth,0);
}
setTimeout(updateAllDisplays,60);
} else {
setTimeout(updateAllDisplays,60);
}
});
mo.observe(document.body,{attributes:true,attributeFilter:['class']});
const wrap = getPreviewWrapper();
if (wrap) {
let pw = wrap.offsetWidth, ph = wrap.offsetHeight;
try {
new ResizeObserver(()=>{
if (
(!editing.width && wrap.offsetWidth!==pw) ||
(!editing.height && wrap.offsetHeight!==ph)
) {
pw = wrap.offsetWidth;
ph = wrap.offsetHeight;
setTimeout(updateAllDisplays,50);
}
}).observe(wrap);
} catch(e){}
}
window.addEventListener('resize',()=>{
setTimeout(updateAllDisplays,80);
},{passive:true});
}
function initOnce() {
createUI();
updateAllDisplays();
observeBreakpointAndWindowResize();
setTimeout(forceMobileMinWidth,30);
}
if (document.readyState==='complete' || document.readyState==='interactive') {
initOnce();
} else {
document.addEventListener('DOMContentLoaded',initOnce);
}
let tries=0;
function tryRecreateUI() {
tries++; if (tries>6) return;
createUI();
setTimeout(()=>tryRecreateUI(),800);
}
try {
if (window.elementor && elementor.channels && elementor.channels.editor) {
elementor.channels.editor.on('document:loaded', ()=>tryRecreateUI());
}
} catch(e){}
setTimeout(tryRecreateUI,1200);
})();
</script>
<style>
/* Minor CSS tweak to avoid overlap and keep visual parity with Elementor buttons */
.e-size-icon-button {
display:flex;align-items:center;justify-content:center;
color:white;width:32px;height:32px;font-size:1.125rem;
border-radius:4px;background:transparent;cursor:pointer;
position:relative;transition:background .16s;margin-left:6px;
flex: 0 0 auto; /* don't shrink */
}
.e-size-icon-button.orientation { font-size:1rem; }
.e-size-icon-button.aspect { font-size:1rem!important; }
.e-size-icon-button.aspect.active { background:#303236; }
.e-size-icon-button.aspect.active:hover { background:#36383c; }
.e-size-icon-button:hover { background:rgba(255,255,255,0.08); }
.e-size-control-wrapper [data-tooltip]:not([data-tooltip=""])::after {
content:attr(data-tooltip);
position:absolute;background:#3F434C;color:#fff;font-size:11px;
padding:4.5px 8px;border-radius:4px;left:50%;transform:translateX(-50%);
opacity:0;transition:.2s;white-space:nowrap;z-index:100000;
}
.e-size-control-wrapper [data-tooltip]:not([data-tooltip=""])::before {
content:"";position:absolute;left:50%;transform:translateX(-50%);
border:12px solid transparent;border-bottom-color:#3F434C;
opacity:0;transition:.2s;z-index:99999;
}
.e-size-control-wrapper [data-tooltip]:hover::after,
.e-size-control-wrapper [data-tooltip]:hover::before {
opacity:1;
}
.MuiBox-root.eui-0.e-size-control-wrapper {
display:flex;align-items:center;gap:.5rem;pointer-events:auto;flex:0 0 auto;
}
.e-size-field { display:inline-flex;align-items:center;gap:.25rem; }
.e-size-field span:not([contenteditable]) { user-select:none; }
.e-size-field span[contenteditable] {
outline:none;user-select:text;text-align:right;
min-width:3ch;padding:0 .125rem;cursor:text;
}
.e-size-aspect-dropdown.eui-1aoepuf {
background:#0c0d0e;color:#fff;box-shadow:0 3px 5px rgba(0,0,0,.2);
border-radius:4px;position:absolute;overflow-y:auto;z-index:99999;
width:7rem;padding:7px 0;margin-top:12px;
}
.e-size-aspect-dropdown button {
background:none;color:#fff;width:100%;border:none;padding:10px 20px;
text-align:left;font-size:15px;cursor:pointer;transition:.15s;
display:flex;align-items:center;gap:.18em;
}
.e-size-aspect-dropdown button:hover { background:rgba(255,255,255,.08); }
</style>
<?php
});
// Load script only inside Elementor Editor
add_action('elementor/editor/after_enqueue_scripts', function () {
?>
<script>
(function () {
function createUI() {
// Prevent duplicate loading
if (document.querySelector('.my-responsive-ui')) return;
// UI WRAPPER
const wrapper = document.createElement('div');
wrapper.className = 'my-responsive-ui';
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.gap = '6px';
wrapper.style.marginLeft = '10px';
wrapper.style.height = '32px';
wrapper.innerHTML = `
<button class="my-resp-btn" data-size="desktop" style="padding:4px 8px;">D</button>
<button class="my-resp-btn" data-size="tablet" style="padding:4px 8px;">T</button>
<button class="my-resp-btn" data-size="mobile" style="padding:4px 8px;">M</button>
`;
let placed = false;
/*
===========================================================
INSERT UI INTO EXACT EDITOR BAR (RED MARKED AREA)
This places the UI into the main middle bar next to
responsive mode icons (desktop/tablet/mobile).
===========================================================
*/
const editorBar =
document.querySelector('.e-responsive-bar') ||
document.querySelector('.elementor-responsive-switchers');
if (editorBar) {
editorBar.appendChild(wrapper);
placed = true;
}
// Secondary fallback — actions header
if (!placed) {
const actions = document.querySelector('.e-editor-header__actions');
if (actions) {
actions.appendChild(wrapper);
placed = true;
}
}
// Final fallback — fixed positioning
if (!placed) {
document.body.appendChild(wrapper);
wrapper.style.position = 'fixed';
wrapper.style.top = '15px';
wrapper.style.left = '300px';
wrapper.style.zIndex = 999999;
}
// Add events to buttons
document.querySelectorAll('.my-resp-btn').forEach(btn => {
btn.addEventListener('click', () => {
const size = btn.dataset.size;
window.dispatchEvent(new CustomEvent('myResponsiveChange', { detail: size }));
});
});
}
// Wait until Elementor editor loads completely
function waitForElementor() {
const interval = setInterval(() => {
if (document.querySelector('.e-editor-header, .elementor-editor-header')) {
clearInterval(interval);
setTimeout(createUI, 300);
}
}, 300);
}
waitForElementor();
})();
</script>
<?php
});