@accelerated-agency/visual-editor 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/vite.cjs +440 -22
  2. package/dist/vite.js +440 -22
  3. package/package.json +1 -1
package/dist/vite.cjs CHANGED
@@ -280,8 +280,97 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
280
280
  .tb-viewport #dev-label{font-size:14px;font-weight:500;color:##404040;min-width:auto;background:none;border:none;padding:0;overflow:visible;white-space:nowrap;text-overflow:clip;border-radius:0;max-width:none}
281
281
  .tb-viewport i{font-size:9px;color:##404040}
282
282
  /* Device toggle buttons */
283
+ .tb-dev-wrap{position:relative;display:flex;align-items:center}
283
284
  .tb-dev-btns{display:flex;align-items:center;gap:1px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 116px;}
284
285
  .tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 100px;}
286
+ .tb-dev-menu{
287
+ position:absolute;top:calc(100% + 8px);right:0;z-index:12040;display:none;
288
+ width:264px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:4px 4px;
289
+ box-shadow:0 12px 28px rgba(2,6,23,.18);color:#27272a
290
+ }
291
+ .tb-dev-menu.open{display:block}
292
+ .tb-dev-menu .hd{font-size:12px;font-weight:600;color:#3f3f46;margin-bottom:10px}
293
+ .tb-dev-menu .row{display:flex;align-items:center;gap:8px;}
294
+ .tb-dev-menu .row.height-width-row .row{
295
+ flex-direction: column;
296
+ align-items: flex-start;
297
+ }
298
+ .tb-dev-menu .row.height-width-row label{
299
+ color: var(--content-subtle, #737373);
300
+ font-size: 12px;
301
+ font-style: normal;
302
+ font-weight: 500;
303
+ line-height: 14px; /* 116.667% */
304
+ }
305
+ .tb-dev-menu > .row{
306
+ padding: 4px 12px;
307
+ }
308
+ .tb-dev-menu label{ flex-shrink: 0;
309
+ overflow: hidden;
310
+ color: var(--base-surface, #646465);
311
+ font-size: var(--font-size-sm, 14px);
312
+ font-style: normal;
313
+ font-weight: 500;
314
+ line-height: var(--font-leading-4, 16px);
315
+ white-space: nowrap;
316
+ min-width: fit-content;}
317
+ .tb-dev-menu input{
318
+ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1px 1px 0 rgba(0, 0, 0, 0.01);
319
+ width:100%;height:30px;border-radius:6px;
320
+ padding:0 8px;
321
+ font-size:12px;
322
+ color:#18181b;
323
+ background:#fff;
324
+ outline:none;
325
+ color:#404040;
326
+ border-color:transparent;
327
+ }
328
+ .tb-dev-menu input#dev-zoom-level{
329
+ max-width: fit-content;
330
+ margin-left: auto;
331
+ }
332
+ .tb-dev-menu input:focus{
333
+ border-color:#1A1A1A;
334
+ box-shadow:0 0 0 2px rgba(99,102,241,.14)
335
+ }
336
+ .tb-dev-menu .row-split > *{flex:1}
337
+ .tb-dev-menu .panel-toggle label{width:100%}
338
+ .tb-dev-menu .panel-toggle-label{
339
+ overflow:hidden;
340
+ color:var(--content-subtle, #737373);
341
+ text-overflow:ellipsis;
342
+ font-size:var(--font-size-sm, 14px);
343
+ font-style:normal;
344
+ font-weight:500;
345
+ line-height:var(--font-leading-4, 16px);
346
+ white-space:nowrap;
347
+ cursor:pointer;
348
+ }
349
+ .tb-dev-menu .vp-presets{
350
+ margin-top:8px;padding-top:8px;border-top:1px dashed #ececf0;
351
+ display:flex;flex-direction:column;gap:2px
352
+ }
353
+ .tb-dev-menu .vp-preset-btn{
354
+ border:none;background:transparent;text-align:left;cursor:pointer;
355
+ border-radius:6px;
356
+ padding:8px 12px;
357
+ color: var(--content-subtle, #737373);
358
+ text-overflow: ellipsis;
359
+ font-size: var(--font-size-sm, 14px);
360
+ font-style: normal;
361
+ font-weight: 500;
362
+ line-height: var(--font-leading-4, 16px);
363
+ }
364
+ .tb-dev-menu .vp-preset-btn:hover,.tb-dev-menu .vp-preset-btn.active{background:#f4f4f5}
365
+ .tb-dev-menu .ft{
366
+ margin-top:10px;padding-top:8px;border-top:1px solid #ececf0;
367
+ display:flex;justify-content:flex-end
368
+ }
369
+ .tb-dev-menu .apply-btn{
370
+ border:1px solid #d4d4d8;background:#f8fafc;color:#111827;border-radius:6px;
371
+ height:30px;padding:0 10px;font-size:12px;font-weight:600;cursor:pointer
372
+ }
373
+ .tb-dev-menu .apply-btn:hover{background:#eef2ff;border-color:#a5b4fc}
285
374
  /* Dark icon buttons */
286
375
  .tb-dk-btn{width:28px;height:28px;background:transparent;border:none;border-radius:5px;cursor:pointer;color:#71717a;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .12s;flex-shrink:0}
287
376
  .tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
@@ -440,7 +529,12 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
440
529
  /* \u2500\u2500 Device frame \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
441
530
  #device-frame{
442
531
  width:100%;min-height:100%;display:flex;align-items:stretch;
443
- justify-content:center;transition:max-width .3s ease
532
+ justify-content:center;transform-origin:top center;
533
+ transition:max-width .3s ease,width .2s ease,height .2s ease
534
+ }
535
+ #device-frame.desktop{
536
+ max-width:1440px;
537
+ box-shadow:0 0 0 1px var(--border),0 4px 24px rgba(0,0,0,.08)
444
538
  }
445
539
  #device-frame.tablet{
446
540
  max-width:768px;
@@ -841,10 +935,8 @@ select.pr-inp{cursor:pointer;background:#fff}
841
935
  </head>
842
936
  <body class="mode-editor">
843
937
  <div id="app">
844
-
845
938
  <!-- \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
846
939
  <div id="toolbar">
847
-
848
940
  <!-- Left: Logo + Breadcrumb -->
849
941
  <div class="tb-left">
850
942
  <span class="tb-logo">\u2733</span>
@@ -862,11 +954,45 @@ select.pr-inp{cursor:pointer;background:#fff}
862
954
  <span id="dev-label">1440px</span>
863
955
  <i class="bi bi-chevron-down"></i>
864
956
  </div>
865
- <div class="tb-dev-btns">
866
- <button class="tb-dk-btn active" id="dev-desktop" onclick="setDevice('desktop')" title="Desktop \u2014 full width"><i class="bi bi-display"></i></button>
867
- <button class="tb-dk-btn" id="dev-tablet" onclick="setDevice('tablet')" title="Tablet \u2014 768px"><i class="bi bi-tablet"></i></button>
868
- <button class="tb-dk-btn" id="dev-mobile" onclick="setDevice('mobile')" title="Mobile \u2014 390px"><i class="bi bi-phone"></i></button>
869
- <button class="tb-dk-btn" title="More options"><i class="bi bi-three-dots"></i></button>
957
+ <div class="tb-dev-wrap">
958
+ <div class="tb-dev-btns">
959
+ <button class="tb-dk-btn active" id="dev-desktop" onclick="setDevice('desktop')" title="Desktop \u2014 1440x1024"><i class="bi bi-display"></i></button>
960
+ <button class="tb-dk-btn" id="dev-tablet" onclick="setDevice('tablet')" title="Tablet \u2014 768x1024"><i class="bi bi-tablet"></i></button>
961
+ <button class="tb-dk-btn" id="dev-mobile" onclick="setDevice('mobile')" title="Mobile \u2014 390x844"><i class="bi bi-phone"></i></button>
962
+ <button class="tb-dk-btn" id="dev-more-btn" title="More options"><i class="bi bi-three-dots"></i></button>
963
+ </div>
964
+ <div id="dev-more-menu" class="tb-dev-menu" aria-hidden="true">
965
+ <div class="row row-zoom">
966
+ <label for="dev-zoom-level">Zoom</label>
967
+ <input id="dev-zoom-level" type="number" min="25" max="200" step="5" value="100" />
968
+ </div>
969
+ <div class="row row-split row-width height-width-row">
970
+ <div class="row" style="margin:0">
971
+ <label for="dev-custom-width">Width</label>
972
+ <input id="dev-custom-width" type="number" min="240" max="3840" step="1" value="1440" />
973
+ </div>
974
+ <div class="row row-height" style="margin:0">
975
+ <label for="dev-custom-height">Height</label>
976
+ <input id="dev-custom-height" type="number" min="320" max="3840" step="1" value="1024" />
977
+ </div>
978
+ </div>
979
+ <div class="row panel-toggle">
980
+ <label id="left-panel-toggle-label" class="panel-toggle-label">Collapse left panel</label>
981
+ </div>
982
+
983
+ <div class="vp-presets" id="dev-preset-list" aria-label="Device presets">
984
+ <button type="button" class="vp-preset-btn" data-preset="desktop">Desktop</button>
985
+ <button type="button" class="vp-preset-btn" data-preset="mobile">iPhone 13</button>
986
+ <button type="button" class="vp-preset-btn" data-preset="galaxy-s22">Galaxy S22</button>
987
+ <button type="button" class="vp-preset-btn" data-preset="tablet">iPad</button>
988
+ <button type="button" class="vp-preset-btn" data-preset="galaxy-j1">Galaxy J1</button>
989
+ <button type="button" class="vp-preset-btn" data-preset="iphone-7">iPhone 7</button>
990
+ <button type="button" class="vp-preset-btn" data-preset="responsive">Responsive</button>
991
+ </div>
992
+ <div class="ft">
993
+ <button type="button" class="apply-btn" id="dev-apply-btn">Apply</button>
994
+ </div>
995
+ </div>
870
996
  </div>
871
997
  <button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
872
998
  <button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)" style="display:none"><i class="bi bi-arrow-clockwise"></i></button>
@@ -1003,7 +1129,7 @@ select.pr-inp{cursor:pointer;background:#fff}
1003
1129
  </div>
1004
1130
 
1005
1131
  <!-- Device frame containing the editing iframe -->
1006
- <div id="device-frame">
1132
+ <div id="device-frame" class="desktop">
1007
1133
  <iframe id="iframeId" name="iframeId" allowfullscreen></iframe>
1008
1134
  </div>
1009
1135
 
@@ -1373,6 +1499,11 @@ var isDirty = false;
1373
1499
  var vvvebReady = false;
1374
1500
  var currentMode = 'editor';
1375
1501
  var currentDevice = 'desktop';
1502
+ var leftPanelCollapsed = false;
1503
+ var viewportPreset = 'desktop';
1504
+ var viewportWidth = 1440;
1505
+ var viewportHeight = 1024;
1506
+ var viewportZoom = 1;
1376
1507
  var selectedEl = null;
1377
1508
  /** Stable selector fingerprint for resilient selection recovery after DOM churn. */
1378
1509
  var selectedElFingerprint = '';
@@ -1625,15 +1756,202 @@ function setMode(mode) {
1625
1756
  }
1626
1757
 
1627
1758
  // \u2500\u2500 Device toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1628
- var DEVICE_LABELS = { desktop: '1440px', tablet: '768px', mobile: '390px' };
1759
+ var DEVICE_PRESETS = {
1760
+ desktop: { label: 'Desktop', width: 1440, height: 1024, device: 'desktop' },
1761
+ tablet: { label: 'iPad', width: 768, height: 1024, device: 'tablet' },
1762
+ mobile: { label: 'iPhone 13', width: 390, height: 844, device: 'mobile' },
1763
+ 'galaxy-s22': { label: 'Galaxy S22', width: 360, height: 780, device: 'mobile' },
1764
+ 'iphone-7': { label: 'iPhone 7', width: 375, height: 667, device: 'mobile' },
1765
+ 'galaxy-j1': { label: 'Galaxy J1', width: 360, height: 640, device: 'mobile' },
1766
+ responsive: { label: 'Responsive', width: 1280, height: 800, device: 'desktop' },
1767
+ };
1768
+
1769
+ function clampViewportNumber(v, fallback, min, max) {
1770
+ var n = parseInt(v, 10);
1771
+ if (!Number.isFinite(n)) return fallback;
1772
+ return Math.max(min, Math.min(max, n));
1773
+ }
1774
+
1775
+ function getViewportFitZoom() {
1776
+ var panel = document.getElementById('iframe-panel');
1777
+ var available = panel ? Math.max(260, panel.clientWidth - 24) : viewportWidth;
1778
+ if (!viewportWidth || viewportWidth <= 0) return 1;
1779
+ return Math.min(1, available / viewportWidth);
1780
+ }
1781
+
1782
+ function getAppliedViewportZoom() {
1783
+ var z = Number(viewportZoom);
1784
+ if (!Number.isFinite(z) || z <= 0) z = 1;
1785
+ z = Math.max(0.25, Math.min(2, z));
1786
+ return Math.min(z, getViewportFitZoom());
1787
+ }
1788
+
1789
+ function updateViewportLabel() {
1790
+ var lbl = document.getElementById('dev-label');
1791
+ if (!lbl) return;
1792
+ var zoomPct = Math.round(getAppliedViewportZoom() * 100);
1793
+ lbl.textContent = viewportWidth + 'x' + viewportHeight + ' \xB7 ' + zoomPct + '%';
1794
+ }
1795
+
1796
+ function syncViewportMenuControls() {
1797
+ var presetButtons = document.querySelectorAll('#dev-preset-list .vp-preset-btn');
1798
+ var widthInp = document.getElementById('dev-custom-width');
1799
+ var heightInp = document.getElementById('dev-custom-height');
1800
+ var zoomInp = document.getElementById('dev-zoom-level');
1801
+ if (presetButtons && presetButtons.length) {
1802
+ for (var i = 0; i < presetButtons.length; i++) {
1803
+ var key = presetButtons[i].getAttribute('data-preset') || '';
1804
+ presetButtons[i].classList.toggle('active', key === viewportPreset);
1805
+ }
1806
+ }
1807
+ if (widthInp) widthInp.value = String(viewportWidth);
1808
+ if (heightInp) heightInp.value = String(viewportHeight);
1809
+ if (zoomInp) zoomInp.value = String(Math.round(getAppliedViewportZoom() * 100));
1810
+ }
1811
+
1812
+ function applyViewportFrame() {
1813
+ var frame = document.getElementById('device-frame');
1814
+ var iframe = document.getElementById('iframeId');
1815
+ if (!frame) return;
1816
+ frame.className = currentDevice;
1817
+ frame.style.width = viewportWidth + 'px';
1818
+ frame.style.height = viewportHeight + 'px';
1819
+ frame.style.maxWidth = 'none';
1820
+ frame.style.zoom = String(getAppliedViewportZoom());
1821
+ if (iframe) {
1822
+ iframe.style.height = viewportHeight + 'px';
1823
+ iframe.style.minHeight = viewportHeight + 'px';
1824
+ }
1825
+ updateViewportLabel();
1826
+ syncViewportMenuControls();
1827
+ if (selectedEl && currentMode === 'editor') requestAnimationFrame(function() { positionSelectionToolbar(); });
1828
+ }
1829
+
1830
+ function setViewportPreset(presetKey) {
1831
+ var preset = DEVICE_PRESETS[presetKey];
1832
+ if (!preset) return;
1833
+ viewportPreset = presetKey;
1834
+ viewportWidth = preset.width;
1835
+ viewportHeight = preset.height;
1836
+ currentDevice = preset.device || 'desktop';
1837
+ ['desktop','tablet','mobile'].forEach(function(d) {
1838
+ document.getElementById('dev-' + d).classList.toggle('active', d === currentDevice);
1839
+ });
1840
+ applyViewportFrame();
1841
+ }
1842
+
1629
1843
  function setDevice(device) {
1844
+ viewportPreset = device;
1630
1845
  currentDevice = device;
1631
- var frame = document.getElementById('device-frame');
1632
- frame.className = device === 'desktop' ? '' : device;
1846
+ var preset = DEVICE_PRESETS[device] || DEVICE_PRESETS.desktop;
1847
+ viewportWidth = preset.width;
1848
+ viewportHeight = preset.height;
1633
1849
  ['desktop','tablet','mobile'].forEach(function(d) {
1634
1850
  document.getElementById('dev-' + d).classList.toggle('active', d === device);
1635
1851
  });
1636
- document.getElementById('dev-label').textContent = DEVICE_LABELS[device] || device;
1852
+ applyViewportFrame();
1853
+ }
1854
+
1855
+ function setViewportCustomFromInputs() {
1856
+ var widthInp = document.getElementById('dev-custom-width');
1857
+ var heightInp = document.getElementById('dev-custom-height');
1858
+ var zoomInp = document.getElementById('dev-zoom-level');
1859
+ viewportWidth = clampViewportNumber(widthInp ? widthInp.value : '', viewportWidth, 240, 3840);
1860
+ viewportHeight = clampViewportNumber(heightInp ? heightInp.value : '', viewportHeight, 320, 3840);
1861
+ var z = clampViewportNumber(zoomInp ? zoomInp.value : '', Math.round(getAppliedViewportZoom() * 100), 25, 200);
1862
+ viewportZoom = z / 100;
1863
+ viewportPreset = 'custom';
1864
+ currentDevice = viewportWidth <= 480 ? 'mobile' : (viewportWidth <= 1024 ? 'tablet' : 'desktop');
1865
+ ['desktop','tablet','mobile'].forEach(function(d) {
1866
+ document.getElementById('dev-' + d).classList.toggle('active', d === currentDevice);
1867
+ });
1868
+ applyViewportFrame();
1869
+ }
1870
+
1871
+ function closeViewportMenu() {
1872
+ var menu = document.getElementById('dev-more-menu');
1873
+ if (!menu) return;
1874
+ menu.classList.remove('open');
1875
+ menu.setAttribute('aria-hidden', 'true');
1876
+ }
1877
+
1878
+ function toggleViewportMenu() {
1879
+ var menu = document.getElementById('dev-more-menu');
1880
+ if (!menu) return;
1881
+ var shouldOpen = !menu.classList.contains('open');
1882
+ menu.classList.toggle('open', shouldOpen);
1883
+ menu.setAttribute('aria-hidden', shouldOpen ? 'false' : 'true');
1884
+ }
1885
+
1886
+ function bindViewportControls() {
1887
+ var btn = document.getElementById('dev-more-btn');
1888
+ var menu = document.getElementById('dev-more-menu');
1889
+ var leftPanelToggleLabel = document.getElementById('left-panel-toggle-label');
1890
+ var presetButtons = document.querySelectorAll('#dev-preset-list .vp-preset-btn');
1891
+ var applyBtn = document.getElementById('dev-apply-btn');
1892
+ var zoomInp = document.getElementById('dev-zoom-level');
1893
+ var viewportBtn = document.querySelector('.tb-viewport');
1894
+ function updateLeftPanelToggleLabel() {
1895
+ if (!leftPanelToggleLabel) return;
1896
+ leftPanelToggleLabel.textContent = leftPanelCollapsed ? 'Expand left panel' : 'Collapse left panel';
1897
+ }
1898
+ function setLeftPanelCollapsed(collapsed) {
1899
+ var panel = document.getElementById('left-panel');
1900
+ leftPanelCollapsed = !!collapsed;
1901
+ if (panel) panel.style.display = leftPanelCollapsed ? 'none' : '';
1902
+ updateLeftPanelToggleLabel();
1903
+ applyViewportFrame();
1904
+ }
1905
+ if (leftPanelToggleLabel) {
1906
+ leftPanelToggleLabel.addEventListener('click', function(e) {
1907
+ e.preventDefault();
1908
+ e.stopPropagation();
1909
+ setLeftPanelCollapsed(!leftPanelCollapsed);
1910
+ });
1911
+ }
1912
+ if (btn) btn.addEventListener('click', function(e) {
1913
+ e.preventDefault();
1914
+ e.stopPropagation();
1915
+ toggleViewportMenu();
1916
+ });
1917
+ if (viewportBtn) viewportBtn.addEventListener('click', function(e) {
1918
+ e.preventDefault();
1919
+ e.stopPropagation();
1920
+ toggleViewportMenu();
1921
+ });
1922
+ if (applyBtn) applyBtn.addEventListener('click', function(e) {
1923
+ e.preventDefault();
1924
+ setViewportCustomFromInputs();
1925
+ closeViewportMenu();
1926
+ });
1927
+ if (zoomInp) {
1928
+ zoomInp.addEventListener('change', function() {
1929
+ var z = clampViewportNumber(zoomInp.value, Math.round(getAppliedViewportZoom() * 100), 25, 200);
1930
+ viewportZoom = z / 100;
1931
+ applyViewportFrame();
1932
+ });
1933
+ }
1934
+ if (presetButtons && presetButtons.length) {
1935
+ for (var i = 0; i < presetButtons.length; i++) {
1936
+ presetButtons[i].addEventListener('click', function(e) {
1937
+ e.preventDefault();
1938
+ var key = this.getAttribute('data-preset');
1939
+ if (!key) return;
1940
+ setViewportPreset(key);
1941
+ });
1942
+ }
1943
+ }
1944
+ document.addEventListener('click', function(e) {
1945
+ if (!menu || !menu.classList.contains('open')) return;
1946
+ if (menu.contains(e.target) || (btn && btn.contains(e.target)) || (viewportBtn && viewportBtn.contains(e.target))) return;
1947
+ closeViewportMenu();
1948
+ });
1949
+ document.addEventListener('keydown', function(e) {
1950
+ if (e.key === 'Escape') closeViewportMenu();
1951
+ });
1952
+ window.addEventListener('resize', function() { applyViewportFrame(); });
1953
+ updateLeftPanelToggleLabel();
1954
+ applyViewportFrame();
1637
1955
  }
1638
1956
 
1639
1957
  // \u2500\u2500 Left panel tab switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1795,9 +2113,60 @@ function getOriginalValue(inputId, el) {
1795
2113
  }
1796
2114
  }
1797
2115
 
2116
+ var PX_DEFAULT_INPUT_IDS = {
2117
+ 'pp-mt': true, 'pp-mr': true, 'pp-mb': true, 'pp-ml': true,
2118
+ 'pp-pt': true, 'pp-pr': true, 'pp-pb': true, 'pp-pl': true,
2119
+ };
2120
+
2121
+ var PX_DEFAULT_CSS_PROPS = {
2122
+ 'margin-top': true, 'margin-right': true, 'margin-bottom': true, 'margin-left': true,
2123
+ 'padding-top': true, 'padding-right': true, 'padding-bottom': true, 'padding-left': true,
2124
+ };
2125
+
2126
+ function normalizeCssValueForProperty(prop, value) {
2127
+ var p = prop == null ? '' : String(prop).trim();
2128
+ var pKebab = p
2129
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
2130
+ .replace(/_/g, '-')
2131
+ .replace(/!important/gi, '')
2132
+ .replace(/[^a-zA-Z-]/g, '')
2133
+ .toLowerCase();
2134
+ var raw = value == null ? '' : String(value).trim();
2135
+ var rawClean = raw.replace(/[\u200B\u200C\u200D\uFEFF]/g, '').trim();
2136
+ var numericCandidate = rawClean.replace(/[, ]+/g, '');
2137
+ var isSpacingProp = /^(?:margin|padding)(?:-(?:top|right|bottom|left))?$/.test(pKebab);
2138
+ if (!pKebab || !rawClean) return rawClean;
2139
+ var numericValue = Number(numericCandidate);
2140
+ var shouldAddPx =
2141
+ isSpacingProp &&
2142
+ numericCandidate !== '' &&
2143
+ Number.isFinite(numericValue) &&
2144
+ /^[-+]?(?:[0-9]+(?:[.][0-9]+)?|[.][0-9]+)$/.test(numericCandidate);
2145
+ var out = shouldAddPx ? numericCandidate + 'px' : rawClean;
2146
+ return out;
2147
+ }
2148
+
2149
+ function normalizeChainSetRowUnits(row) {
2150
+ if (!row) return row;
2151
+ if (normalizeChangesetType(row) === 'style') {
2152
+ row.value = normalizeCssValueForProperty(row.property || row.cssProp, row.value);
2153
+ }
2154
+ return row;
2155
+ }
2156
+
2157
+ function normalizeLoggedValue(inputId, value) {
2158
+ var raw = value == null ? '' : String(value).trim();
2159
+ if (!raw) return raw;
2160
+ if (PX_DEFAULT_INPUT_IDS[inputId] && /^-?d+(?:.d+)?$/.test(raw)) {
2161
+ return raw + 'px';
2162
+ }
2163
+ return raw;
2164
+ }
2165
+
1798
2166
  function logChange(selector, inputId, value, targetEl, originalValue) {
1799
2167
  var meta = PROP_META[inputId];
1800
2168
  if (!meta) return;
2169
+ value = normalizeLoggedValue(inputId, value);
1801
2170
  var key = selector + '||' + inputId;
1802
2171
  // Skip trivially empty / reset values \u2014 remove from log if present
1803
2172
  if (value === '' || value === 'none' || value === 'auto' || value === 'normal') {
@@ -2496,9 +2865,24 @@ function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPr
2496
2865
  return fallback;
2497
2866
  }
2498
2867
 
2868
+ function updateExperimentNameLabel(data) {
2869
+ var el = document.getElementById('tb-exp-name');
2870
+ if (!el) return;
2871
+ var name = '';
2872
+ try {
2873
+ name = data && data.name != null ? String(data.name).trim() : '';
2874
+ } catch(_) {
2875
+ name = '';
2876
+ }
2877
+ if (!name) name = 'Visual Editor';
2878
+ el.textContent = name;
2879
+ el.title = name;
2880
+ }
2881
+
2499
2882
  // \u2500\u2500 Experiment loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2500
2883
  function handleLoadExperiment(data) {
2501
2884
  clearPendingGranularChangesets();
2885
+ updateExperimentNameLabel(data);
2502
2886
  var prevKey = experimentData
2503
2887
  ? String(experimentData.experimentId || '') + '|' + String(experimentData.pageUrl || '')
2504
2888
  : '';
@@ -3140,7 +3524,13 @@ function removeSessionStructuralRowByTimestamp(varId, ts) {
3140
3524
  function stateChangeToChainSet(c) {
3141
3525
  if (!c || !c.selector) return null;
3142
3526
  if (c.cssProp) {
3143
- return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3527
+ return {
3528
+ selector: c.selector,
3529
+ type: 'style',
3530
+ property: c.cssProp,
3531
+ value: normalizeCssValueForProperty(c.cssProp, c.value),
3532
+ vveTs: c.vveTs || nextHistoryTimestamp()
3533
+ };
3144
3534
  }
3145
3535
  switch (c.inputId) {
3146
3536
  case 'pp-text':
@@ -3312,7 +3702,7 @@ function buildPersistentStyleRulesForActiveVariation() {
3312
3702
  if (value == null || value === '') return;
3313
3703
  var sel = sanitizeSelectorForMatch(String(selector)) || String(selector);
3314
3704
  var pr = String(prop).trim();
3315
- var val = String(value).trim();
3705
+ var val = normalizeCssValueForProperty(pr, value);
3316
3706
  if (!sel || !pr || !val) return;
3317
3707
  var k = sel + '__vve_sep__' + pr;
3318
3708
  if (!map[k]) order.push(k);
@@ -3350,7 +3740,8 @@ function buildPersistentStyleRulesForActiveVariation() {
3350
3740
  var lines = [];
3351
3741
  for (var oi = 0; oi < order.length; oi++) {
3352
3742
  var row = map[order[oi]];
3353
- lines.push(row.selector + ' { ' + row.property + ': ' + row.value + ' !important; }');
3743
+ var outVal = normalizeCssValueForProperty(row.property, row.value);
3744
+ lines.push(row.selector + ' { ' + row.property + ': ' + outVal + ' !important; }');
3354
3745
  }
3355
3746
  return lines.join('\\n');
3356
3747
  }
@@ -3501,7 +3892,9 @@ function buildPersistedChainSetsForVariation(v) {
3501
3892
  var row = stateChangeToChainSet(sourceStateChanges[si]);
3502
3893
  if (row) overlay.push(row);
3503
3894
  }
3504
- return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
3895
+ var merged = mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
3896
+ for (var mi = 0; mi < merged.length; mi++) normalizeChainSetRowUnits(merged[mi]);
3897
+ return merged;
3505
3898
  }
3506
3899
 
3507
3900
  /**
@@ -5403,6 +5796,7 @@ function registerCROSections() {
5403
5796
 
5404
5797
  window.addEventListener('load', function() {
5405
5798
  registerCROSections();
5799
+ bindViewportControls();
5406
5800
  switchSectionComponentsTab(currentSectionComponentsTab);
5407
5801
  renderElementsTree(document.getElementById('comp-search').value);
5408
5802
  vvvebReady = true;
@@ -6110,8 +6504,32 @@ ${runtimePreflightScript}
6110
6504
  var TARGET_ORIGIN=${JSON.stringify(origin)};
6111
6505
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
6112
6506
  var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";
6113
- function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}
6114
- function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}
6507
+ var _proxyCtx=(function(){try{return new URL(window.location.href);}catch(_){return null;}})();
6508
+ var PROXY_ROOT="/api/conversion-proxy";
6509
+ var PROXY_PASSWORD=_proxyCtx?_proxyCtx.searchParams.get("password")||"":"";
6510
+ var PROXY_BASE_URL=_proxyCtx?_proxyCtx.searchParams.get("conversionProxyBaseUrl")||"":"";
6511
+ var PROXY_TRACKING_MARKERS=_proxyCtx?_proxyCtx.searchParams.get("trackingMarkers")||"":"";
6512
+ var PROXY_STRICT_FREEZE=_proxyCtx?_proxyCtx.searchParams.get("strictObserverFreeze")||"":"";
6513
+ var PROXY_UPSTREAM_MODE=_proxyCtx?_proxyCtx.searchParams.get("proxy")||"":"";
6514
+ function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
6515
+ function toProxyNetworkUrl(raw){
6516
+ if(isSkippable(raw))return raw;
6517
+ try{
6518
+ var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;
6519
+ var abs=new URL(raw,base);
6520
+ if(abs.origin!==TARGET_ORIGIN)return raw;
6521
+ var prox=new URL(PROXY_ROOT,window.location.origin);
6522
+ prox.searchParams.set("password",PROXY_PASSWORD||"");
6523
+ prox.searchParams.set("url",abs.toString());
6524
+ if(PROXY_BASE_URL)prox.searchParams.set("conversionProxyBaseUrl",PROXY_BASE_URL);
6525
+ if(PROXY_TRACKING_MARKERS)prox.searchParams.set("trackingMarkers",PROXY_TRACKING_MARKERS);
6526
+ if(PROXY_STRICT_FREEZE)prox.searchParams.set("strictObserverFreeze",PROXY_STRICT_FREEZE);
6527
+ if(PROXY_UPSTREAM_MODE)prox.searchParams.set("proxy",PROXY_UPSTREAM_MODE);
6528
+ return prox.toString();
6529
+ }catch(_){
6530
+ return raw;
6531
+ }
6532
+ }
6115
6533
  function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}
6116
6534
  function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}
6117
6535
  function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}
@@ -6124,9 +6542,9 @@ if(window.fetch){
6124
6542
  var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
6125
6543
  if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();
6126
6544
  if(typeof input==="string"){
6127
- input=toAbsoluteOriginUrl(input);
6545
+ input=toProxyNetworkUrl(input);
6128
6546
  }else if(input&&input.url){
6129
- var next=toAbsoluteOriginUrl(input.url);
6547
+ var next=toProxyNetworkUrl(input.url);
6130
6548
  if(next!==input.url){input=new Request(next,input);}
6131
6549
  }
6132
6550
  afterUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
@@ -6160,7 +6578,7 @@ if(window.fetch){
6160
6578
  });
6161
6579
  };
6162
6580
  }
6163
- if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
6581
+ if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toProxyNetworkUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
6164
6582
  if(window.navigator&&typeof window.navigator.sendBeacon==="function"){var _beacon=window.navigator.sendBeacon.bind(window.navigator);window.navigator.sendBeacon=function(url,data){try{if(skipNestedProxyNetwork(String(url)))return true;}catch(_){}return _beacon(url,data);};}
6165
6583
  function withReloadBust(urlRaw){try{var u=resolveUrl(String(urlRaw||""));if(!u)return null;var p=u.pathname||"";var isRootProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0;if(!isRootProxyPath)return null;return u.toString();}catch(_){return null;}}
6166
6584
  document.addEventListener("click",function(ev){try{var t=ev&&ev.target;if(!t||!t.closest)return;var a=t.closest("a[href]");if(a){var href=a.getAttribute("href")||a.href||"";var hasModifier=!!(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey);var targetAttr=(a.getAttribute("target")||"").toLowerCase();var isNewTab=targetAttr==="_blank";var isDownload=!!a.getAttribute("download");if(hasModifier||isNewTab||isDownload)return;var prox=toProxy(href);if(!prox)return;a.setAttribute("href",prox);if(ev&&typeof ev.preventDefault==="function")ev.preventDefault();safeNavigate(href,"assign");return;}var summary=t.closest("summary[data-link]");if(summary&&summary.getAttribute){var raw=summary.getAttribute("data-link")||"";if(raw){var prox2=toProxy(raw);if(prox2){summary.setAttribute("data-link",prox2);if(ev&&typeof ev.preventDefault==="function")ev.preventDefault();safeNavigate(raw,"assign");}}}}catch(_){}},true);
package/dist/vite.js CHANGED
@@ -272,8 +272,97 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
272
272
  .tb-viewport #dev-label{font-size:14px;font-weight:500;color:##404040;min-width:auto;background:none;border:none;padding:0;overflow:visible;white-space:nowrap;text-overflow:clip;border-radius:0;max-width:none}
273
273
  .tb-viewport i{font-size:9px;color:##404040}
274
274
  /* Device toggle buttons */
275
+ .tb-dev-wrap{position:relative;display:flex;align-items:center}
275
276
  .tb-dev-btns{display:flex;align-items:center;gap:1px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 116px;}
276
277
  .tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 100px;}
278
+ .tb-dev-menu{
279
+ position:absolute;top:calc(100% + 8px);right:0;z-index:12040;display:none;
280
+ width:264px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:4px 4px;
281
+ box-shadow:0 12px 28px rgba(2,6,23,.18);color:#27272a
282
+ }
283
+ .tb-dev-menu.open{display:block}
284
+ .tb-dev-menu .hd{font-size:12px;font-weight:600;color:#3f3f46;margin-bottom:10px}
285
+ .tb-dev-menu .row{display:flex;align-items:center;gap:8px;}
286
+ .tb-dev-menu .row.height-width-row .row{
287
+ flex-direction: column;
288
+ align-items: flex-start;
289
+ }
290
+ .tb-dev-menu .row.height-width-row label{
291
+ color: var(--content-subtle, #737373);
292
+ font-size: 12px;
293
+ font-style: normal;
294
+ font-weight: 500;
295
+ line-height: 14px; /* 116.667% */
296
+ }
297
+ .tb-dev-menu > .row{
298
+ padding: 4px 12px;
299
+ }
300
+ .tb-dev-menu label{ flex-shrink: 0;
301
+ overflow: hidden;
302
+ color: var(--base-surface, #646465);
303
+ font-size: var(--font-size-sm, 14px);
304
+ font-style: normal;
305
+ font-weight: 500;
306
+ line-height: var(--font-leading-4, 16px);
307
+ white-space: nowrap;
308
+ min-width: fit-content;}
309
+ .tb-dev-menu input{
310
+ box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.20), 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0 1px 1px 0 rgba(0, 0, 0, 0.01);
311
+ width:100%;height:30px;border-radius:6px;
312
+ padding:0 8px;
313
+ font-size:12px;
314
+ color:#18181b;
315
+ background:#fff;
316
+ outline:none;
317
+ color:#404040;
318
+ border-color:transparent;
319
+ }
320
+ .tb-dev-menu input#dev-zoom-level{
321
+ max-width: fit-content;
322
+ margin-left: auto;
323
+ }
324
+ .tb-dev-menu input:focus{
325
+ border-color:#1A1A1A;
326
+ box-shadow:0 0 0 2px rgba(99,102,241,.14)
327
+ }
328
+ .tb-dev-menu .row-split > *{flex:1}
329
+ .tb-dev-menu .panel-toggle label{width:100%}
330
+ .tb-dev-menu .panel-toggle-label{
331
+ overflow:hidden;
332
+ color:var(--content-subtle, #737373);
333
+ text-overflow:ellipsis;
334
+ font-size:var(--font-size-sm, 14px);
335
+ font-style:normal;
336
+ font-weight:500;
337
+ line-height:var(--font-leading-4, 16px);
338
+ white-space:nowrap;
339
+ cursor:pointer;
340
+ }
341
+ .tb-dev-menu .vp-presets{
342
+ margin-top:8px;padding-top:8px;border-top:1px dashed #ececf0;
343
+ display:flex;flex-direction:column;gap:2px
344
+ }
345
+ .tb-dev-menu .vp-preset-btn{
346
+ border:none;background:transparent;text-align:left;cursor:pointer;
347
+ border-radius:6px;
348
+ padding:8px 12px;
349
+ color: var(--content-subtle, #737373);
350
+ text-overflow: ellipsis;
351
+ font-size: var(--font-size-sm, 14px);
352
+ font-style: normal;
353
+ font-weight: 500;
354
+ line-height: var(--font-leading-4, 16px);
355
+ }
356
+ .tb-dev-menu .vp-preset-btn:hover,.tb-dev-menu .vp-preset-btn.active{background:#f4f4f5}
357
+ .tb-dev-menu .ft{
358
+ margin-top:10px;padding-top:8px;border-top:1px solid #ececf0;
359
+ display:flex;justify-content:flex-end
360
+ }
361
+ .tb-dev-menu .apply-btn{
362
+ border:1px solid #d4d4d8;background:#f8fafc;color:#111827;border-radius:6px;
363
+ height:30px;padding:0 10px;font-size:12px;font-weight:600;cursor:pointer
364
+ }
365
+ .tb-dev-menu .apply-btn:hover{background:#eef2ff;border-color:#a5b4fc}
277
366
  /* Dark icon buttons */
278
367
  .tb-dk-btn{width:28px;height:28px;background:transparent;border:none;border-radius:5px;cursor:pointer;color:#71717a;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .12s;flex-shrink:0}
279
368
  .tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
@@ -432,7 +521,12 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
432
521
  /* \u2500\u2500 Device frame \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
433
522
  #device-frame{
434
523
  width:100%;min-height:100%;display:flex;align-items:stretch;
435
- justify-content:center;transition:max-width .3s ease
524
+ justify-content:center;transform-origin:top center;
525
+ transition:max-width .3s ease,width .2s ease,height .2s ease
526
+ }
527
+ #device-frame.desktop{
528
+ max-width:1440px;
529
+ box-shadow:0 0 0 1px var(--border),0 4px 24px rgba(0,0,0,.08)
436
530
  }
437
531
  #device-frame.tablet{
438
532
  max-width:768px;
@@ -833,10 +927,8 @@ select.pr-inp{cursor:pointer;background:#fff}
833
927
  </head>
834
928
  <body class="mode-editor">
835
929
  <div id="app">
836
-
837
930
  <!-- \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
838
931
  <div id="toolbar">
839
-
840
932
  <!-- Left: Logo + Breadcrumb -->
841
933
  <div class="tb-left">
842
934
  <span class="tb-logo">\u2733</span>
@@ -854,11 +946,45 @@ select.pr-inp{cursor:pointer;background:#fff}
854
946
  <span id="dev-label">1440px</span>
855
947
  <i class="bi bi-chevron-down"></i>
856
948
  </div>
857
- <div class="tb-dev-btns">
858
- <button class="tb-dk-btn active" id="dev-desktop" onclick="setDevice('desktop')" title="Desktop \u2014 full width"><i class="bi bi-display"></i></button>
859
- <button class="tb-dk-btn" id="dev-tablet" onclick="setDevice('tablet')" title="Tablet \u2014 768px"><i class="bi bi-tablet"></i></button>
860
- <button class="tb-dk-btn" id="dev-mobile" onclick="setDevice('mobile')" title="Mobile \u2014 390px"><i class="bi bi-phone"></i></button>
861
- <button class="tb-dk-btn" title="More options"><i class="bi bi-three-dots"></i></button>
949
+ <div class="tb-dev-wrap">
950
+ <div class="tb-dev-btns">
951
+ <button class="tb-dk-btn active" id="dev-desktop" onclick="setDevice('desktop')" title="Desktop \u2014 1440x1024"><i class="bi bi-display"></i></button>
952
+ <button class="tb-dk-btn" id="dev-tablet" onclick="setDevice('tablet')" title="Tablet \u2014 768x1024"><i class="bi bi-tablet"></i></button>
953
+ <button class="tb-dk-btn" id="dev-mobile" onclick="setDevice('mobile')" title="Mobile \u2014 390x844"><i class="bi bi-phone"></i></button>
954
+ <button class="tb-dk-btn" id="dev-more-btn" title="More options"><i class="bi bi-three-dots"></i></button>
955
+ </div>
956
+ <div id="dev-more-menu" class="tb-dev-menu" aria-hidden="true">
957
+ <div class="row row-zoom">
958
+ <label for="dev-zoom-level">Zoom</label>
959
+ <input id="dev-zoom-level" type="number" min="25" max="200" step="5" value="100" />
960
+ </div>
961
+ <div class="row row-split row-width height-width-row">
962
+ <div class="row" style="margin:0">
963
+ <label for="dev-custom-width">Width</label>
964
+ <input id="dev-custom-width" type="number" min="240" max="3840" step="1" value="1440" />
965
+ </div>
966
+ <div class="row row-height" style="margin:0">
967
+ <label for="dev-custom-height">Height</label>
968
+ <input id="dev-custom-height" type="number" min="320" max="3840" step="1" value="1024" />
969
+ </div>
970
+ </div>
971
+ <div class="row panel-toggle">
972
+ <label id="left-panel-toggle-label" class="panel-toggle-label">Collapse left panel</label>
973
+ </div>
974
+
975
+ <div class="vp-presets" id="dev-preset-list" aria-label="Device presets">
976
+ <button type="button" class="vp-preset-btn" data-preset="desktop">Desktop</button>
977
+ <button type="button" class="vp-preset-btn" data-preset="mobile">iPhone 13</button>
978
+ <button type="button" class="vp-preset-btn" data-preset="galaxy-s22">Galaxy S22</button>
979
+ <button type="button" class="vp-preset-btn" data-preset="tablet">iPad</button>
980
+ <button type="button" class="vp-preset-btn" data-preset="galaxy-j1">Galaxy J1</button>
981
+ <button type="button" class="vp-preset-btn" data-preset="iphone-7">iPhone 7</button>
982
+ <button type="button" class="vp-preset-btn" data-preset="responsive">Responsive</button>
983
+ </div>
984
+ <div class="ft">
985
+ <button type="button" class="apply-btn" id="dev-apply-btn">Apply</button>
986
+ </div>
987
+ </div>
862
988
  </div>
863
989
  <button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
864
990
  <button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)" style="display:none"><i class="bi bi-arrow-clockwise"></i></button>
@@ -995,7 +1121,7 @@ select.pr-inp{cursor:pointer;background:#fff}
995
1121
  </div>
996
1122
 
997
1123
  <!-- Device frame containing the editing iframe -->
998
- <div id="device-frame">
1124
+ <div id="device-frame" class="desktop">
999
1125
  <iframe id="iframeId" name="iframeId" allowfullscreen></iframe>
1000
1126
  </div>
1001
1127
 
@@ -1365,6 +1491,11 @@ var isDirty = false;
1365
1491
  var vvvebReady = false;
1366
1492
  var currentMode = 'editor';
1367
1493
  var currentDevice = 'desktop';
1494
+ var leftPanelCollapsed = false;
1495
+ var viewportPreset = 'desktop';
1496
+ var viewportWidth = 1440;
1497
+ var viewportHeight = 1024;
1498
+ var viewportZoom = 1;
1368
1499
  var selectedEl = null;
1369
1500
  /** Stable selector fingerprint for resilient selection recovery after DOM churn. */
1370
1501
  var selectedElFingerprint = '';
@@ -1617,15 +1748,202 @@ function setMode(mode) {
1617
1748
  }
1618
1749
 
1619
1750
  // \u2500\u2500 Device toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1620
- var DEVICE_LABELS = { desktop: '1440px', tablet: '768px', mobile: '390px' };
1751
+ var DEVICE_PRESETS = {
1752
+ desktop: { label: 'Desktop', width: 1440, height: 1024, device: 'desktop' },
1753
+ tablet: { label: 'iPad', width: 768, height: 1024, device: 'tablet' },
1754
+ mobile: { label: 'iPhone 13', width: 390, height: 844, device: 'mobile' },
1755
+ 'galaxy-s22': { label: 'Galaxy S22', width: 360, height: 780, device: 'mobile' },
1756
+ 'iphone-7': { label: 'iPhone 7', width: 375, height: 667, device: 'mobile' },
1757
+ 'galaxy-j1': { label: 'Galaxy J1', width: 360, height: 640, device: 'mobile' },
1758
+ responsive: { label: 'Responsive', width: 1280, height: 800, device: 'desktop' },
1759
+ };
1760
+
1761
+ function clampViewportNumber(v, fallback, min, max) {
1762
+ var n = parseInt(v, 10);
1763
+ if (!Number.isFinite(n)) return fallback;
1764
+ return Math.max(min, Math.min(max, n));
1765
+ }
1766
+
1767
+ function getViewportFitZoom() {
1768
+ var panel = document.getElementById('iframe-panel');
1769
+ var available = panel ? Math.max(260, panel.clientWidth - 24) : viewportWidth;
1770
+ if (!viewportWidth || viewportWidth <= 0) return 1;
1771
+ return Math.min(1, available / viewportWidth);
1772
+ }
1773
+
1774
+ function getAppliedViewportZoom() {
1775
+ var z = Number(viewportZoom);
1776
+ if (!Number.isFinite(z) || z <= 0) z = 1;
1777
+ z = Math.max(0.25, Math.min(2, z));
1778
+ return Math.min(z, getViewportFitZoom());
1779
+ }
1780
+
1781
+ function updateViewportLabel() {
1782
+ var lbl = document.getElementById('dev-label');
1783
+ if (!lbl) return;
1784
+ var zoomPct = Math.round(getAppliedViewportZoom() * 100);
1785
+ lbl.textContent = viewportWidth + 'x' + viewportHeight + ' \xB7 ' + zoomPct + '%';
1786
+ }
1787
+
1788
+ function syncViewportMenuControls() {
1789
+ var presetButtons = document.querySelectorAll('#dev-preset-list .vp-preset-btn');
1790
+ var widthInp = document.getElementById('dev-custom-width');
1791
+ var heightInp = document.getElementById('dev-custom-height');
1792
+ var zoomInp = document.getElementById('dev-zoom-level');
1793
+ if (presetButtons && presetButtons.length) {
1794
+ for (var i = 0; i < presetButtons.length; i++) {
1795
+ var key = presetButtons[i].getAttribute('data-preset') || '';
1796
+ presetButtons[i].classList.toggle('active', key === viewportPreset);
1797
+ }
1798
+ }
1799
+ if (widthInp) widthInp.value = String(viewportWidth);
1800
+ if (heightInp) heightInp.value = String(viewportHeight);
1801
+ if (zoomInp) zoomInp.value = String(Math.round(getAppliedViewportZoom() * 100));
1802
+ }
1803
+
1804
+ function applyViewportFrame() {
1805
+ var frame = document.getElementById('device-frame');
1806
+ var iframe = document.getElementById('iframeId');
1807
+ if (!frame) return;
1808
+ frame.className = currentDevice;
1809
+ frame.style.width = viewportWidth + 'px';
1810
+ frame.style.height = viewportHeight + 'px';
1811
+ frame.style.maxWidth = 'none';
1812
+ frame.style.zoom = String(getAppliedViewportZoom());
1813
+ if (iframe) {
1814
+ iframe.style.height = viewportHeight + 'px';
1815
+ iframe.style.minHeight = viewportHeight + 'px';
1816
+ }
1817
+ updateViewportLabel();
1818
+ syncViewportMenuControls();
1819
+ if (selectedEl && currentMode === 'editor') requestAnimationFrame(function() { positionSelectionToolbar(); });
1820
+ }
1821
+
1822
+ function setViewportPreset(presetKey) {
1823
+ var preset = DEVICE_PRESETS[presetKey];
1824
+ if (!preset) return;
1825
+ viewportPreset = presetKey;
1826
+ viewportWidth = preset.width;
1827
+ viewportHeight = preset.height;
1828
+ currentDevice = preset.device || 'desktop';
1829
+ ['desktop','tablet','mobile'].forEach(function(d) {
1830
+ document.getElementById('dev-' + d).classList.toggle('active', d === currentDevice);
1831
+ });
1832
+ applyViewportFrame();
1833
+ }
1834
+
1621
1835
  function setDevice(device) {
1836
+ viewportPreset = device;
1622
1837
  currentDevice = device;
1623
- var frame = document.getElementById('device-frame');
1624
- frame.className = device === 'desktop' ? '' : device;
1838
+ var preset = DEVICE_PRESETS[device] || DEVICE_PRESETS.desktop;
1839
+ viewportWidth = preset.width;
1840
+ viewportHeight = preset.height;
1625
1841
  ['desktop','tablet','mobile'].forEach(function(d) {
1626
1842
  document.getElementById('dev-' + d).classList.toggle('active', d === device);
1627
1843
  });
1628
- document.getElementById('dev-label').textContent = DEVICE_LABELS[device] || device;
1844
+ applyViewportFrame();
1845
+ }
1846
+
1847
+ function setViewportCustomFromInputs() {
1848
+ var widthInp = document.getElementById('dev-custom-width');
1849
+ var heightInp = document.getElementById('dev-custom-height');
1850
+ var zoomInp = document.getElementById('dev-zoom-level');
1851
+ viewportWidth = clampViewportNumber(widthInp ? widthInp.value : '', viewportWidth, 240, 3840);
1852
+ viewportHeight = clampViewportNumber(heightInp ? heightInp.value : '', viewportHeight, 320, 3840);
1853
+ var z = clampViewportNumber(zoomInp ? zoomInp.value : '', Math.round(getAppliedViewportZoom() * 100), 25, 200);
1854
+ viewportZoom = z / 100;
1855
+ viewportPreset = 'custom';
1856
+ currentDevice = viewportWidth <= 480 ? 'mobile' : (viewportWidth <= 1024 ? 'tablet' : 'desktop');
1857
+ ['desktop','tablet','mobile'].forEach(function(d) {
1858
+ document.getElementById('dev-' + d).classList.toggle('active', d === currentDevice);
1859
+ });
1860
+ applyViewportFrame();
1861
+ }
1862
+
1863
+ function closeViewportMenu() {
1864
+ var menu = document.getElementById('dev-more-menu');
1865
+ if (!menu) return;
1866
+ menu.classList.remove('open');
1867
+ menu.setAttribute('aria-hidden', 'true');
1868
+ }
1869
+
1870
+ function toggleViewportMenu() {
1871
+ var menu = document.getElementById('dev-more-menu');
1872
+ if (!menu) return;
1873
+ var shouldOpen = !menu.classList.contains('open');
1874
+ menu.classList.toggle('open', shouldOpen);
1875
+ menu.setAttribute('aria-hidden', shouldOpen ? 'false' : 'true');
1876
+ }
1877
+
1878
+ function bindViewportControls() {
1879
+ var btn = document.getElementById('dev-more-btn');
1880
+ var menu = document.getElementById('dev-more-menu');
1881
+ var leftPanelToggleLabel = document.getElementById('left-panel-toggle-label');
1882
+ var presetButtons = document.querySelectorAll('#dev-preset-list .vp-preset-btn');
1883
+ var applyBtn = document.getElementById('dev-apply-btn');
1884
+ var zoomInp = document.getElementById('dev-zoom-level');
1885
+ var viewportBtn = document.querySelector('.tb-viewport');
1886
+ function updateLeftPanelToggleLabel() {
1887
+ if (!leftPanelToggleLabel) return;
1888
+ leftPanelToggleLabel.textContent = leftPanelCollapsed ? 'Expand left panel' : 'Collapse left panel';
1889
+ }
1890
+ function setLeftPanelCollapsed(collapsed) {
1891
+ var panel = document.getElementById('left-panel');
1892
+ leftPanelCollapsed = !!collapsed;
1893
+ if (panel) panel.style.display = leftPanelCollapsed ? 'none' : '';
1894
+ updateLeftPanelToggleLabel();
1895
+ applyViewportFrame();
1896
+ }
1897
+ if (leftPanelToggleLabel) {
1898
+ leftPanelToggleLabel.addEventListener('click', function(e) {
1899
+ e.preventDefault();
1900
+ e.stopPropagation();
1901
+ setLeftPanelCollapsed(!leftPanelCollapsed);
1902
+ });
1903
+ }
1904
+ if (btn) btn.addEventListener('click', function(e) {
1905
+ e.preventDefault();
1906
+ e.stopPropagation();
1907
+ toggleViewportMenu();
1908
+ });
1909
+ if (viewportBtn) viewportBtn.addEventListener('click', function(e) {
1910
+ e.preventDefault();
1911
+ e.stopPropagation();
1912
+ toggleViewportMenu();
1913
+ });
1914
+ if (applyBtn) applyBtn.addEventListener('click', function(e) {
1915
+ e.preventDefault();
1916
+ setViewportCustomFromInputs();
1917
+ closeViewportMenu();
1918
+ });
1919
+ if (zoomInp) {
1920
+ zoomInp.addEventListener('change', function() {
1921
+ var z = clampViewportNumber(zoomInp.value, Math.round(getAppliedViewportZoom() * 100), 25, 200);
1922
+ viewportZoom = z / 100;
1923
+ applyViewportFrame();
1924
+ });
1925
+ }
1926
+ if (presetButtons && presetButtons.length) {
1927
+ for (var i = 0; i < presetButtons.length; i++) {
1928
+ presetButtons[i].addEventListener('click', function(e) {
1929
+ e.preventDefault();
1930
+ var key = this.getAttribute('data-preset');
1931
+ if (!key) return;
1932
+ setViewportPreset(key);
1933
+ });
1934
+ }
1935
+ }
1936
+ document.addEventListener('click', function(e) {
1937
+ if (!menu || !menu.classList.contains('open')) return;
1938
+ if (menu.contains(e.target) || (btn && btn.contains(e.target)) || (viewportBtn && viewportBtn.contains(e.target))) return;
1939
+ closeViewportMenu();
1940
+ });
1941
+ document.addEventListener('keydown', function(e) {
1942
+ if (e.key === 'Escape') closeViewportMenu();
1943
+ });
1944
+ window.addEventListener('resize', function() { applyViewportFrame(); });
1945
+ updateLeftPanelToggleLabel();
1946
+ applyViewportFrame();
1629
1947
  }
1630
1948
 
1631
1949
  // \u2500\u2500 Left panel tab switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1787,9 +2105,60 @@ function getOriginalValue(inputId, el) {
1787
2105
  }
1788
2106
  }
1789
2107
 
2108
+ var PX_DEFAULT_INPUT_IDS = {
2109
+ 'pp-mt': true, 'pp-mr': true, 'pp-mb': true, 'pp-ml': true,
2110
+ 'pp-pt': true, 'pp-pr': true, 'pp-pb': true, 'pp-pl': true,
2111
+ };
2112
+
2113
+ var PX_DEFAULT_CSS_PROPS = {
2114
+ 'margin-top': true, 'margin-right': true, 'margin-bottom': true, 'margin-left': true,
2115
+ 'padding-top': true, 'padding-right': true, 'padding-bottom': true, 'padding-left': true,
2116
+ };
2117
+
2118
+ function normalizeCssValueForProperty(prop, value) {
2119
+ var p = prop == null ? '' : String(prop).trim();
2120
+ var pKebab = p
2121
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
2122
+ .replace(/_/g, '-')
2123
+ .replace(/!important/gi, '')
2124
+ .replace(/[^a-zA-Z-]/g, '')
2125
+ .toLowerCase();
2126
+ var raw = value == null ? '' : String(value).trim();
2127
+ var rawClean = raw.replace(/[\u200B\u200C\u200D\uFEFF]/g, '').trim();
2128
+ var numericCandidate = rawClean.replace(/[, ]+/g, '');
2129
+ var isSpacingProp = /^(?:margin|padding)(?:-(?:top|right|bottom|left))?$/.test(pKebab);
2130
+ if (!pKebab || !rawClean) return rawClean;
2131
+ var numericValue = Number(numericCandidate);
2132
+ var shouldAddPx =
2133
+ isSpacingProp &&
2134
+ numericCandidate !== '' &&
2135
+ Number.isFinite(numericValue) &&
2136
+ /^[-+]?(?:[0-9]+(?:[.][0-9]+)?|[.][0-9]+)$/.test(numericCandidate);
2137
+ var out = shouldAddPx ? numericCandidate + 'px' : rawClean;
2138
+ return out;
2139
+ }
2140
+
2141
+ function normalizeChainSetRowUnits(row) {
2142
+ if (!row) return row;
2143
+ if (normalizeChangesetType(row) === 'style') {
2144
+ row.value = normalizeCssValueForProperty(row.property || row.cssProp, row.value);
2145
+ }
2146
+ return row;
2147
+ }
2148
+
2149
+ function normalizeLoggedValue(inputId, value) {
2150
+ var raw = value == null ? '' : String(value).trim();
2151
+ if (!raw) return raw;
2152
+ if (PX_DEFAULT_INPUT_IDS[inputId] && /^-?d+(?:.d+)?$/.test(raw)) {
2153
+ return raw + 'px';
2154
+ }
2155
+ return raw;
2156
+ }
2157
+
1790
2158
  function logChange(selector, inputId, value, targetEl, originalValue) {
1791
2159
  var meta = PROP_META[inputId];
1792
2160
  if (!meta) return;
2161
+ value = normalizeLoggedValue(inputId, value);
1793
2162
  var key = selector + '||' + inputId;
1794
2163
  // Skip trivially empty / reset values \u2014 remove from log if present
1795
2164
  if (value === '' || value === 'none' || value === 'auto' || value === 'normal') {
@@ -2488,9 +2857,24 @@ function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPr
2488
2857
  return fallback;
2489
2858
  }
2490
2859
 
2860
+ function updateExperimentNameLabel(data) {
2861
+ var el = document.getElementById('tb-exp-name');
2862
+ if (!el) return;
2863
+ var name = '';
2864
+ try {
2865
+ name = data && data.name != null ? String(data.name).trim() : '';
2866
+ } catch(_) {
2867
+ name = '';
2868
+ }
2869
+ if (!name) name = 'Visual Editor';
2870
+ el.textContent = name;
2871
+ el.title = name;
2872
+ }
2873
+
2491
2874
  // \u2500\u2500 Experiment loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2492
2875
  function handleLoadExperiment(data) {
2493
2876
  clearPendingGranularChangesets();
2877
+ updateExperimentNameLabel(data);
2494
2878
  var prevKey = experimentData
2495
2879
  ? String(experimentData.experimentId || '') + '|' + String(experimentData.pageUrl || '')
2496
2880
  : '';
@@ -3132,7 +3516,13 @@ function removeSessionStructuralRowByTimestamp(varId, ts) {
3132
3516
  function stateChangeToChainSet(c) {
3133
3517
  if (!c || !c.selector) return null;
3134
3518
  if (c.cssProp) {
3135
- return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
3519
+ return {
3520
+ selector: c.selector,
3521
+ type: 'style',
3522
+ property: c.cssProp,
3523
+ value: normalizeCssValueForProperty(c.cssProp, c.value),
3524
+ vveTs: c.vveTs || nextHistoryTimestamp()
3525
+ };
3136
3526
  }
3137
3527
  switch (c.inputId) {
3138
3528
  case 'pp-text':
@@ -3304,7 +3694,7 @@ function buildPersistentStyleRulesForActiveVariation() {
3304
3694
  if (value == null || value === '') return;
3305
3695
  var sel = sanitizeSelectorForMatch(String(selector)) || String(selector);
3306
3696
  var pr = String(prop).trim();
3307
- var val = String(value).trim();
3697
+ var val = normalizeCssValueForProperty(pr, value);
3308
3698
  if (!sel || !pr || !val) return;
3309
3699
  var k = sel + '__vve_sep__' + pr;
3310
3700
  if (!map[k]) order.push(k);
@@ -3342,7 +3732,8 @@ function buildPersistentStyleRulesForActiveVariation() {
3342
3732
  var lines = [];
3343
3733
  for (var oi = 0; oi < order.length; oi++) {
3344
3734
  var row = map[order[oi]];
3345
- lines.push(row.selector + ' { ' + row.property + ': ' + row.value + ' !important; }');
3735
+ var outVal = normalizeCssValueForProperty(row.property, row.value);
3736
+ lines.push(row.selector + ' { ' + row.property + ': ' + outVal + ' !important; }');
3346
3737
  }
3347
3738
  return lines.join('\\n');
3348
3739
  }
@@ -3493,7 +3884,9 @@ function buildPersistedChainSetsForVariation(v) {
3493
3884
  var row = stateChangeToChainSet(sourceStateChanges[si]);
3494
3885
  if (row) overlay.push(row);
3495
3886
  }
3496
- return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
3887
+ var merged = mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
3888
+ for (var mi = 0; mi < merged.length; mi++) normalizeChainSetRowUnits(merged[mi]);
3889
+ return merged;
3497
3890
  }
3498
3891
 
3499
3892
  /**
@@ -5395,6 +5788,7 @@ function registerCROSections() {
5395
5788
 
5396
5789
  window.addEventListener('load', function() {
5397
5790
  registerCROSections();
5791
+ bindViewportControls();
5398
5792
  switchSectionComponentsTab(currentSectionComponentsTab);
5399
5793
  renderElementsTree(document.getElementById('comp-search').value);
5400
5794
  vvvebReady = true;
@@ -6102,8 +6496,32 @@ ${runtimePreflightScript}
6102
6496
  var TARGET_ORIGIN=${JSON.stringify(origin)};
6103
6497
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
6104
6498
  var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";
6105
- function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}
6106
- function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}
6499
+ var _proxyCtx=(function(){try{return new URL(window.location.href);}catch(_){return null;}})();
6500
+ var PROXY_ROOT="/api/conversion-proxy";
6501
+ var PROXY_PASSWORD=_proxyCtx?_proxyCtx.searchParams.get("password")||"":"";
6502
+ var PROXY_BASE_URL=_proxyCtx?_proxyCtx.searchParams.get("conversionProxyBaseUrl")||"":"";
6503
+ var PROXY_TRACKING_MARKERS=_proxyCtx?_proxyCtx.searchParams.get("trackingMarkers")||"":"";
6504
+ var PROXY_STRICT_FREEZE=_proxyCtx?_proxyCtx.searchParams.get("strictObserverFreeze")||"":"";
6505
+ var PROXY_UPSTREAM_MODE=_proxyCtx?_proxyCtx.searchParams.get("proxy")||"":"";
6506
+ function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
6507
+ function toProxyNetworkUrl(raw){
6508
+ if(isSkippable(raw))return raw;
6509
+ try{
6510
+ var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;
6511
+ var abs=new URL(raw,base);
6512
+ if(abs.origin!==TARGET_ORIGIN)return raw;
6513
+ var prox=new URL(PROXY_ROOT,window.location.origin);
6514
+ prox.searchParams.set("password",PROXY_PASSWORD||"");
6515
+ prox.searchParams.set("url",abs.toString());
6516
+ if(PROXY_BASE_URL)prox.searchParams.set("conversionProxyBaseUrl",PROXY_BASE_URL);
6517
+ if(PROXY_TRACKING_MARKERS)prox.searchParams.set("trackingMarkers",PROXY_TRACKING_MARKERS);
6518
+ if(PROXY_STRICT_FREEZE)prox.searchParams.set("strictObserverFreeze",PROXY_STRICT_FREEZE);
6519
+ if(PROXY_UPSTREAM_MODE)prox.searchParams.set("proxy",PROXY_UPSTREAM_MODE);
6520
+ return prox.toString();
6521
+ }catch(_){
6522
+ return raw;
6523
+ }
6524
+ }
6107
6525
  function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}
6108
6526
  function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}
6109
6527
  function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}
@@ -6116,9 +6534,9 @@ if(window.fetch){
6116
6534
  var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
6117
6535
  if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();
6118
6536
  if(typeof input==="string"){
6119
- input=toAbsoluteOriginUrl(input);
6537
+ input=toProxyNetworkUrl(input);
6120
6538
  }else if(input&&input.url){
6121
- var next=toAbsoluteOriginUrl(input.url);
6539
+ var next=toProxyNetworkUrl(input.url);
6122
6540
  if(next!==input.url){input=new Request(next,input);}
6123
6541
  }
6124
6542
  afterUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
@@ -6152,7 +6570,7 @@ if(window.fetch){
6152
6570
  });
6153
6571
  };
6154
6572
  }
6155
- if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
6573
+ if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toProxyNetworkUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
6156
6574
  if(window.navigator&&typeof window.navigator.sendBeacon==="function"){var _beacon=window.navigator.sendBeacon.bind(window.navigator);window.navigator.sendBeacon=function(url,data){try{if(skipNestedProxyNetwork(String(url)))return true;}catch(_){}return _beacon(url,data);};}
6157
6575
  function withReloadBust(urlRaw){try{var u=resolveUrl(String(urlRaw||""));if(!u)return null;var p=u.pathname||"";var isRootProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0;if(!isRootProxyPath)return null;return u.toString();}catch(_){return null;}}
6158
6576
  document.addEventListener("click",function(ev){try{var t=ev&&ev.target;if(!t||!t.closest)return;var a=t.closest("a[href]");if(a){var href=a.getAttribute("href")||a.href||"";var hasModifier=!!(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey);var targetAttr=(a.getAttribute("target")||"").toLowerCase();var isNewTab=targetAttr==="_blank";var isDownload=!!a.getAttribute("download");if(hasModifier||isNewTab||isDownload)return;var prox=toProxy(href);if(!prox)return;a.setAttribute("href",prox);if(ev&&typeof ev.preventDefault==="function")ev.preventDefault();safeNavigate(href,"assign");return;}var summary=t.closest("summary[data-link]");if(summary&&summary.getAttribute){var raw=summary.getAttribute("data-link")||"";if(raw){var prox2=toProxy(raw);if(prox2){summary.setAttribute("data-link",prox2);if(ev&&typeof ev.preventDefault==="function")ev.preventDefault();safeNavigate(raw,"assign");}}}}catch(_){}},true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",