284 lines
8.7 KiB
JavaScript
284 lines
8.7 KiB
JavaScript
(function(global) {
|
|
'use strict';
|
|
|
|
function initMobileSync() {
|
|
function usesStaticSidebarLayout() {
|
|
return global.innerWidth > 1100 || global.matchMedia('(max-width: 780px) and (orientation: landscape)').matches;
|
|
}
|
|
|
|
function canUseSidebarFocusMode() {
|
|
return global.innerWidth > 1100;
|
|
}
|
|
|
|
function syncSidebarFocusUi() {
|
|
var focusToggle = document.getElementById('sidebarFocusToggle');
|
|
var focusOpen = document.body.classList.contains('sidebar-focus-open');
|
|
if (!focusToggle) return;
|
|
|
|
focusToggle.setAttribute('aria-pressed', focusOpen ? 'true' : 'false');
|
|
focusToggle.setAttribute('aria-label', focusOpen ? 'Collapse live quote' : 'Expand live quote');
|
|
focusToggle.setAttribute('title', focusOpen ? 'Collapse live quote' : 'Expand live quote');
|
|
}
|
|
|
|
function buildMobileSidebar() {
|
|
var container = document.getElementById('mobilePanelContent');
|
|
var desktopSidebar = document.querySelector('.side-col .sidebar');
|
|
if (!container || !desktopSidebar || container.children.length) return;
|
|
|
|
var mobileSidebar = desktopSidebar.cloneNode(true);
|
|
mobileSidebar.removeAttribute('id');
|
|
|
|
mobileSidebar.querySelectorAll('[id]').forEach(function(el) {
|
|
el.id = el.id + '_m';
|
|
});
|
|
|
|
var mobileHstToggle = mobileSidebar.querySelector('#hstToggle_m');
|
|
if (mobileHstToggle) {
|
|
mobileHstToggle.onchange = function() {
|
|
var desktopHstToggle = document.getElementById('hstToggle');
|
|
if (desktopHstToggle) desktopHstToggle.checked = this.checked;
|
|
global.update();
|
|
};
|
|
}
|
|
|
|
var mobileExportJson = mobileSidebar.querySelector('#btnExportJSON_m');
|
|
if (mobileExportJson) mobileExportJson.removeAttribute('id');
|
|
|
|
container.appendChild(mobileSidebar);
|
|
}
|
|
|
|
function syncEl(id) {
|
|
var src = document.getElementById(id);
|
|
var dst = document.getElementById(id + '_m');
|
|
if (src && dst) dst.innerHTML = src.innerHTML;
|
|
}
|
|
|
|
function syncClass(id) {
|
|
var src = document.getElementById(id);
|
|
var dst = document.getElementById(id + '_m');
|
|
if (src && dst) dst.className = src.className;
|
|
}
|
|
|
|
function syncStyle(id) {
|
|
var src = document.getElementById(id);
|
|
var dst = document.getElementById(id + '_m');
|
|
if (src && dst) dst.style.cssText = src.style.cssText;
|
|
}
|
|
|
|
function syncChecked(id) {
|
|
var src = document.getElementById(id);
|
|
var dst = document.getElementById(id + '_m');
|
|
if (src && dst) dst.checked = src.checked;
|
|
}
|
|
|
|
function runSidebarSync(ids, syncFn) {
|
|
ids.forEach(syncFn);
|
|
}
|
|
|
|
var sidebarSyncMap = {
|
|
html: [
|
|
'sl-users-val',
|
|
'sl-endpoints-val',
|
|
'sl-servers-val',
|
|
'sl-zt-val',
|
|
'sl-voip-val',
|
|
'sl-admin-val',
|
|
'sl-monthly-total-val',
|
|
'mrrDisplay',
|
|
'sl-discount-val',
|
|
'sl-discount-detail',
|
|
'sl-base-mrr-val',
|
|
'sl-hst-val',
|
|
'sl-hst-total-val',
|
|
'sl-first-mri-val',
|
|
'sl-first-hst-val',
|
|
'sl-otf-val',
|
|
'sl-first-total-val',
|
|
'sl-value-m365-val',
|
|
'sl-value-term-val',
|
|
'sl-value-onboarding-val',
|
|
'sl-value-onboarding-label',
|
|
'sl-value-admin-val',
|
|
'sl-value-byol-val',
|
|
'sl-value-total-val',
|
|
'annualDisplay',
|
|
'perUserDisplay',
|
|
'perUserBreakdown',
|
|
'sidebarFocusClientName',
|
|
'vs-svs-annual',
|
|
'vs-1man-cost',
|
|
'vs-1man-save',
|
|
'vs-1man-save-lbl',
|
|
'vs-5man-cost',
|
|
'vs-5man-save',
|
|
'vs-5man-save-lbl',
|
|
'vs-footnote',
|
|
'nudgeText',
|
|
'nudgeCounter',
|
|
'sl-users-sub',
|
|
'sl-endpoints-sub',
|
|
'sl-admin-sub'
|
|
],
|
|
class: [
|
|
'sl-users',
|
|
'sl-users-sub',
|
|
'sl-endpoints',
|
|
'sl-endpoints-sub',
|
|
'sl-admin-sub',
|
|
'sl-servers',
|
|
'sl-zt',
|
|
'sl-voip',
|
|
'sl-admin',
|
|
'sl-monthly-total-row',
|
|
'vsComparison',
|
|
'sl-first-hst-row',
|
|
'sl-otf-row',
|
|
'sl-first-total-row',
|
|
'sl-value-m365-row',
|
|
'sl-value-term-row',
|
|
'sl-value-onboarding-row',
|
|
'sl-value-admin-row',
|
|
'sl-value-byol-row',
|
|
'sl-value-total-row',
|
|
'perUserRow',
|
|
'perUserBreakdown',
|
|
'sl-discount-row',
|
|
'sl-base-mrr-row',
|
|
'sl-hst-row',
|
|
'sl-hst-total-row',
|
|
'vs-1man-save-row',
|
|
'vs-1man-save',
|
|
'vs-1man-save-lbl',
|
|
'vs-5man-save-row',
|
|
'vs-5man-save',
|
|
'vs-5man-save-lbl',
|
|
'nudgeBanner'
|
|
],
|
|
style: [
|
|
'sl-users-sub',
|
|
'sl-endpoints-sub',
|
|
'sl-admin-sub',
|
|
'perUserRow'
|
|
],
|
|
checked: [
|
|
'hstToggle'
|
|
]
|
|
};
|
|
|
|
buildMobileSidebar();
|
|
|
|
global.openSidebarFocus = function() {
|
|
if (!canUseSidebarFocusMode()) return;
|
|
document.body.classList.add('sidebar-focus-open');
|
|
syncSidebarFocusUi();
|
|
if (typeof global.syncBodyScrollLock === 'function') global.syncBodyScrollLock();
|
|
};
|
|
|
|
global.closeSidebarFocus = function() {
|
|
document.body.classList.remove('sidebar-focus-open');
|
|
syncSidebarFocusUi();
|
|
if (typeof global.syncBodyScrollLock === 'function') global.syncBodyScrollLock();
|
|
};
|
|
|
|
global.toggleSidebarFocus = function() {
|
|
if (document.body.classList.contains('sidebar-focus-open')) global.closeSidebarFocus();
|
|
else global.openSidebarFocus();
|
|
};
|
|
|
|
// Focus trap for overlays (mobile panel, sidebar focus)
|
|
function trapFocus(container, e) {
|
|
var focusable = container.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
if (!focusable.length) return;
|
|
var first = focusable[0];
|
|
var last = focusable[focusable.length - 1];
|
|
if (e.shiftKey && document.activeElement === first) {
|
|
e.preventDefault();
|
|
last.focus();
|
|
} else if (!e.shiftKey && document.activeElement === last) {
|
|
e.preventDefault();
|
|
first.focus();
|
|
}
|
|
}
|
|
|
|
global.openMobilePanel = function() {
|
|
var panel = document.getElementById('mobileQuotePanel');
|
|
if (panel) {
|
|
panel.classList.add('open');
|
|
// Force full sync so mobile panel has fresh data
|
|
runSidebarSync(sidebarSyncMap.html, syncEl);
|
|
runSidebarSync(sidebarSyncMap.class, syncClass);
|
|
runSidebarSync(sidebarSyncMap.style, syncStyle);
|
|
runSidebarSync(sidebarSyncMap.checked, syncChecked);
|
|
global.syncBodyScrollLock();
|
|
// Move focus into the panel
|
|
var closeBtn = panel.querySelector('.mobile-panel-close-btn');
|
|
if (closeBtn) closeBtn.focus();
|
|
}
|
|
};
|
|
|
|
global.closeMobilePanel = function() {
|
|
var panel = document.getElementById('mobileQuotePanel');
|
|
if (panel) {
|
|
panel.classList.remove('open');
|
|
global.syncBodyScrollLock();
|
|
// Return focus to the pill that opened the panel
|
|
var pill = document.querySelector('.mobile-quote-pill');
|
|
if (pill) pill.focus();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
global.closeMobilePanel();
|
|
global.closeSidebarFocus();
|
|
}
|
|
// Focus trap: keep Tab cycling within the open overlay
|
|
if (e.key === 'Tab') {
|
|
var panel = document.getElementById('mobileQuotePanel');
|
|
if (panel && panel.classList.contains('open')) {
|
|
trapFocus(panel, e);
|
|
}
|
|
}
|
|
});
|
|
|
|
global.addEventListener('resize', function() {
|
|
if (usesStaticSidebarLayout()) global.closeMobilePanel();
|
|
if (!canUseSidebarFocusMode()) global.closeSidebarFocus();
|
|
});
|
|
|
|
var originalUpdate = global.update;
|
|
global.update = function() {
|
|
originalUpdate();
|
|
|
|
// Only sync mobile panel elements when panel is open or pill is visible
|
|
var panel = document.getElementById('mobileQuotePanel');
|
|
var panelOpen = panel && panel.classList.contains('open');
|
|
var isMobileViewport = global.innerWidth <= 1100;
|
|
|
|
if (panelOpen || isMobileViewport) {
|
|
runSidebarSync(sidebarSyncMap.html, syncEl);
|
|
runSidebarSync(sidebarSyncMap.class, syncClass);
|
|
runSidebarSync(sidebarSyncMap.style, syncStyle);
|
|
runSidebarSync(sidebarSyncMap.checked, syncChecked);
|
|
}
|
|
|
|
// Always sync the pill MRR (lightweight, one element)
|
|
var mrr = document.getElementById('mrrDisplay');
|
|
var pill = document.getElementById('mobilePillMrr');
|
|
if (mrr && pill) pill.textContent = mrr.textContent;
|
|
syncSidebarFocusUi();
|
|
};
|
|
|
|
syncSidebarFocusUi();
|
|
global.update();
|
|
}
|
|
|
|
global.SVSQuoteMobileSync = {
|
|
initMobileSync
|
|
};
|
|
|
|
initMobileSync();
|
|
})(window);
|