uivisor 0.1.2 → 0.1.4

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.
@@ -96,12 +96,13 @@ function withUivisor(nextConfig = {}, options = {}) {
96
96
  }
97
97
  if (useTurbopack) {
98
98
  const ld = [{ loader: "uivisor/next/loader", options: { attr } }];
99
+ const major = nextMajor();
100
+ const modern = major === 0 || major >= 15;
99
101
  const rules = {
100
- "*.tsx": { loaders: ld, as: "*.tsx" },
101
- "*.jsx": { loaders: ld, as: "*.jsx" }
102
+ "*.tsx": modern ? { loaders: ld } : { loaders: ld, as: "*.tsx" },
103
+ "*.jsx": modern ? { loaders: ld } : { loaders: ld, as: "*.jsx" }
102
104
  };
103
- const major = nextMajor();
104
- if (major === 0 || major >= 15) {
105
+ if (modern) {
105
106
  out.turbopack = {
106
107
  ...nextConfig.turbopack,
107
108
  rules: { ...nextConfig.turbopack?.rules ?? {}, ...rules }
@@ -68,12 +68,13 @@ function withUivisor(nextConfig = {}, options = {}) {
68
68
  }
69
69
  if (useTurbopack) {
70
70
  const ld = [{ loader: "uivisor/next/loader", options: { attr } }];
71
+ const major = nextMajor();
72
+ const modern = major === 0 || major >= 15;
71
73
  const rules = {
72
- "*.tsx": { loaders: ld, as: "*.tsx" },
73
- "*.jsx": { loaders: ld, as: "*.jsx" }
74
+ "*.tsx": modern ? { loaders: ld } : { loaders: ld, as: "*.tsx" },
75
+ "*.jsx": modern ? { loaders: ld } : { loaders: ld, as: "*.jsx" }
74
76
  };
75
- const major = nextMajor();
76
- if (major === 0 || major >= 15) {
77
+ if (modern) {
77
78
  out.turbopack = {
78
79
  ...nextConfig.turbopack,
79
80
  rules: { ...nextConfig.turbopack?.rules ?? {}, ...rules }
@@ -253,6 +253,62 @@ function suggestUtility(property, value) {
253
253
  if (!hex) return null;
254
254
  return property === "color" ? `text-[${hex}]` : `bg-[${hex}]`;
255
255
  }
256
+ const LAYOUT = {
257
+ display: {
258
+ block: "block",
259
+ "inline-block": "inline-block",
260
+ inline: "inline",
261
+ flex: "flex",
262
+ "inline-flex": "inline-flex",
263
+ grid: "grid",
264
+ "inline-grid": "inline-grid",
265
+ none: "hidden"
266
+ },
267
+ "flex-direction": {
268
+ row: "flex-row",
269
+ "row-reverse": "flex-row-reverse",
270
+ column: "flex-col",
271
+ "column-reverse": "flex-col-reverse"
272
+ },
273
+ "flex-wrap": { nowrap: "flex-nowrap", wrap: "flex-wrap", "wrap-reverse": "flex-wrap-reverse" },
274
+ "justify-content": {
275
+ normal: "justify-normal",
276
+ "flex-start": "justify-start",
277
+ center: "justify-center",
278
+ "flex-end": "justify-end",
279
+ "space-between": "justify-between",
280
+ "space-around": "justify-around",
281
+ "space-evenly": "justify-evenly"
282
+ },
283
+ "align-items": {
284
+ normal: "items-normal",
285
+ stretch: "items-stretch",
286
+ "flex-start": "items-start",
287
+ center: "items-center",
288
+ "flex-end": "items-end",
289
+ baseline: "items-baseline"
290
+ }
291
+ };
292
+ if (LAYOUT[property]) return LAYOUT[property][value.trim()] ?? null;
293
+ const SIZE_PREFIX = {
294
+ width: "w",
295
+ height: "h",
296
+ "min-width": "min-w",
297
+ "max-width": "max-w",
298
+ "min-height": "min-h",
299
+ "max-height": "max-h"
300
+ };
301
+ if (SIZE_PREFIX[property]) {
302
+ const pre = SIZE_PREFIX[property];
303
+ const t = value.trim();
304
+ if (t === "auto") return `${pre}-auto`;
305
+ if (t === "100%") return `${pre}-full`;
306
+ const n = px(t);
307
+ if (n == null) return `${pre}-[${t}]`;
308
+ const scale = n / 4;
309
+ if (Number.isInteger(scale) && scale >= 0 && scale <= 96) return `${pre}-${scale}`;
310
+ return `${pre}-[${n}px]`;
311
+ }
256
312
  return null;
257
313
  }
258
314
 
@@ -306,9 +362,61 @@ var ICONS = {
306
362
  line: sv('<path d="M2.5 3 H13.5 M2.5 8 H13.5 M2.5 13 H13.5"/>'),
307
363
  tracking: sv('<path d="M5 4 V12 M11 4 V12"/><path d="M2.5 8 H4 M1.8 6.6 L0.8 8 L1.8 9.4"/><path d="M13.5 8 H12 M14.2 6.6 L15.2 8 L14.2 9.4"/>'),
308
364
  expand: sv('<rect x="2" y="2" width="4.5" height="4.5" rx="1"/><rect x="9.5" y="2" width="4.5" height="4.5" rx="1"/><rect x="2" y="9.5" width="4.5" height="4.5" rx="1"/><rect x="9.5" y="9.5" width="4.5" height="4.5" rx="1"/>'),
309
- collapse: sv('<rect x="3" y="3" width="10" height="10" rx="2"/>')
365
+ collapse: sv('<rect x="3" y="3" width="10" height="10" rx="2"/>'),
366
+ layout: sv('<rect x="2" y="2" width="12" height="12" rx="1.5"/><path d="M6 2.5 V13.5 M6 6 H13.5"/>'),
367
+ width: sv('<path d="M1.5 8 H14.5 M4 5 L1.5 8 L4 11 M12 5 L14.5 8 L12 11"/>'),
368
+ height: sv('<path d="M8 1.5 V14.5 M5 4 L8 1.5 L11 4 M5 12 L8 14.5 L11 12"/>'),
369
+ chevron: sv('<path d="M6 4 L10 8 L6 12"/>')
310
370
  };
311
371
  var SECTIONS = [
372
+ {
373
+ title: "Layout",
374
+ controls: [
375
+ {
376
+ kind: "select",
377
+ css: "display",
378
+ label: "Display",
379
+ options: ["block", "inline-block", "inline", "flex", "inline-flex", "grid", "inline-grid", "none"]
380
+ },
381
+ {
382
+ kind: "select",
383
+ css: "flex-direction",
384
+ label: "Direction",
385
+ options: ["row", "row-reverse", "column", "column-reverse"],
386
+ requires: "flexgrid"
387
+ },
388
+ {
389
+ kind: "select",
390
+ css: "justify-content",
391
+ label: "Justify",
392
+ options: ["normal", "flex-start", "center", "flex-end", "space-between", "space-around", "space-evenly"],
393
+ requires: "flexgrid"
394
+ },
395
+ {
396
+ kind: "select",
397
+ css: "align-items",
398
+ label: "Align",
399
+ options: ["normal", "stretch", "flex-start", "center", "flex-end", "baseline"],
400
+ requires: "flexgrid"
401
+ },
402
+ {
403
+ kind: "select",
404
+ css: "flex-wrap",
405
+ label: "Wrap",
406
+ options: ["nowrap", "wrap", "wrap-reverse"],
407
+ requires: "flexgrid"
408
+ }
409
+ ]
410
+ },
411
+ {
412
+ title: "Size",
413
+ controls: [
414
+ { kind: "len", css: "width", label: "Width", icon: ICONS.width },
415
+ { kind: "len", css: "height", label: "Height", icon: ICONS.height },
416
+ { kind: "len", css: "max-width", label: "Max W", icon: ICONS.width },
417
+ { kind: "len", css: "min-height", label: "Min H", icon: ICONS.height }
418
+ ]
419
+ },
312
420
  {
313
421
  title: "Spacing",
314
422
  controls: [
@@ -909,6 +1017,17 @@ var CSS = (
909
1017
  letter-spacing: .5px; color: #8b8b94; font-weight: 600; }
910
1018
  .uiv-sec + .uiv-sec .uiv-sectitle { margin-top: 0; }
911
1019
 
1020
+ /* collapsible (accordion) section header */
1021
+ .uiv-acc { display: flex; align-items: center; gap: 5px; width: 100%;
1022
+ background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
1023
+ font-size: 10px; text-transform: uppercase; letter-spacing: .5px;
1024
+ color: #8b8b94; font-weight: 600; }
1025
+ .uiv-acc:hover { color: #c7d2fe; }
1026
+ .uiv-acc.collapsed { margin-bottom: 0; }
1027
+ .uiv-chev { display: inline-flex; color: #6b6b73; transition: transform .15s ease; }
1028
+ .uiv-acc:hover .uiv-chev { color: #818cf8; }
1029
+ .uiv-acc:not(.collapsed) .uiv-chev { transform: rotate(90deg); }
1030
+
912
1031
  .uiv-ctl { display: grid; grid-template-columns: 70px 1fr 26px; gap: 8px;
913
1032
  align-items: center; margin-bottom: 7px; }
914
1033
  .uiv-ctl:last-child { margin-bottom: 0; }
@@ -997,6 +1116,14 @@ var Uivisor = class {
997
1116
  this.selected = null;
998
1117
  this.states = /* @__PURE__ */ new Map();
999
1118
  this.expanded = /* @__PURE__ */ new Set();
1119
+ /** Section titles collapsed in the accordion (per session). */
1120
+ this.collapsedSecs = /* @__PURE__ */ new Set();
1121
+ /** Undo / redo stacks of full edit-state snapshots. */
1122
+ this.undoStack = [];
1123
+ this.redoStack = [];
1124
+ /** Cached live computed-style for the selected element (invalidated on reselect). */
1125
+ this._cs = null;
1126
+ this._csEl = null;
1000
1127
  /** Cached project breakpoint system (detected from CSS), refreshed until found. */
1001
1128
  this._bp = null;
1002
1129
  // responsive (virtual screen) mode
@@ -1032,7 +1159,20 @@ var Uivisor = class {
1032
1159
  if (e.altKey && (e.key === "u" || e.key === "U")) {
1033
1160
  e.preventDefault();
1034
1161
  this.toggle();
1035
- } else if (e.key === "Escape" && this.enabled) {
1162
+ return;
1163
+ }
1164
+ if (!this.enabled) return;
1165
+ if ((e.metaKey || e.ctrlKey) && (e.key === "z" || e.key === "Z")) {
1166
+ e.preventDefault();
1167
+ e.shiftKey ? this.redo() : this.undo();
1168
+ return;
1169
+ }
1170
+ if ((e.metaKey || e.ctrlKey) && (e.key === "y" || e.key === "Y")) {
1171
+ e.preventDefault();
1172
+ this.redo();
1173
+ return;
1174
+ }
1175
+ if (e.key === "Escape") {
1036
1176
  if (this.selected) this.select(null);
1037
1177
  else this.toggle(false);
1038
1178
  }
@@ -1270,7 +1410,6 @@ var Uivisor = class {
1270
1410
  const up = () => {
1271
1411
  handle.removeEventListener("pointermove", move);
1272
1412
  handle.removeEventListener("pointerup", up);
1273
- this.retagSelected();
1274
1413
  this.renderBody();
1275
1414
  };
1276
1415
  handle.addEventListener("pointermove", move);
@@ -1313,11 +1452,26 @@ var Uivisor = class {
1313
1452
  st() {
1314
1453
  return this.selected ? this.states.get(this.selected) ?? null : null;
1315
1454
  }
1455
+ /** Live computed value of the selected element — reflects the CURRENT breakpoint
1456
+ * (the virtual screen's width / real window), unlike the at-selection snapshot. */
1457
+ computedVal(css) {
1458
+ const el = this.selected;
1459
+ if (!el) return "";
1460
+ if (this._csEl !== el || !this._cs) {
1461
+ try {
1462
+ this._cs = getComputedStyle(el);
1463
+ this._csEl = el;
1464
+ } catch {
1465
+ return "";
1466
+ }
1467
+ }
1468
+ return this._cs.getPropertyValue(css).trim();
1469
+ }
1316
1470
  liveVal(css) {
1317
1471
  const el = this.selected;
1318
1472
  const st = this.st();
1319
1473
  if (!el || !st) return "";
1320
- return el.style.getPropertyValue(css) || st.original[css] || "";
1474
+ return el.style.getPropertyValue(css) || this.computedVal(css) || st.original[css] || "";
1321
1475
  }
1322
1476
  liveNum(css) {
1323
1477
  const v = this.liveVal(css).trim();
@@ -1403,16 +1557,6 @@ var Uivisor = class {
1403
1557
  if (this.responsive) return activeBreakpoint(this.frameWidth, sys);
1404
1558
  return currentBreakpoint(sys);
1405
1559
  }
1406
- /** Re-tag the selected element's already-recorded changes to the current scope. */
1407
- retagSelected() {
1408
- const st = this.st();
1409
- if (!st) return;
1410
- const scope = this.activeScope();
1411
- for (const c of st.record.changes) {
1412
- c.breakpoint = scope.name;
1413
- c.breakpointPx = scope.minWidth;
1414
- }
1415
- }
1416
1560
  recordProps(cssList) {
1417
1561
  const el = this.selected;
1418
1562
  const st = this.st();
@@ -1492,7 +1636,7 @@ var Uivisor = class {
1492
1636
  body.innerHTML = `
1493
1637
  ${this.breakpointBarHtml()}
1494
1638
  <div class="uiv-empty">Click any element ${this.responsive ? "in the frame" : "on the page"} to select it.</div>
1495
- <div class="uiv-hint">Alt+U toggles \xB7 Esc deselects. Tweaks stay in the browser \u2014 nothing is written to your code.</div>
1639
+ <div class="uiv-hint">Alt+U toggles \xB7 Esc deselects \xB7 \u2318/Ctrl+Z undo, \u21E7 to redo. Tweaks stay in the browser \u2014 nothing is written to your code.</div>
1496
1640
  ${this.journalHtml()}
1497
1641
  `;
1498
1642
  this.bindControls();
@@ -1583,8 +1727,9 @@ var Uivisor = class {
1583
1727
  if (g("box-shadow") !== "none" && g("box-shadow")) add("shadow", "yes");
1584
1728
  const op = g("opacity");
1585
1729
  if (op && parseFloat(op) < 1) add("opacity", op);
1586
- const items = rows.map((r) => `<div class="uiv-rrow"><span class="uiv-rk">${r.k}</span><span class="uiv-rv${r.edited ? " changed" : ""}">${r.v}</span></div>`).join("");
1587
- return `<div class="uiv-sec"><div class="uiv-sectitle">Current styles</div><div class="uiv-readout">${items}</div></div>`;
1730
+ const collapsed = this.collapsedSecs.has("Current styles");
1731
+ const items = collapsed ? "" : rows.map((r) => `<div class="uiv-rrow"><span class="uiv-rk">${r.k}</span><span class="uiv-rv${r.edited ? " changed" : ""}">${r.v}</span></div>`).join("");
1732
+ return `<div class="uiv-sec">${this.accordionTitle("Current styles")}<div class="uiv-readout">${items}</div></div>`;
1588
1733
  }
1589
1734
  /** Breakpoint scope switcher: shows the PROJECT's breakpoints + the live window one. */
1590
1735
  breakpointBarHtml() {
@@ -1640,10 +1785,15 @@ var Uivisor = class {
1640
1785
  return SECTIONS.map((sec) => {
1641
1786
  const controls = sec.controls.filter((c) => this.relevant(c, ctx));
1642
1787
  if (!controls.length) return "";
1643
- const rows = controls.map((c) => this.controlRow(c)).join("");
1644
- return `<div class="uiv-sec"><div class="uiv-sectitle">${sec.title}</div>${rows}</div>`;
1788
+ const rows = this.collapsedSecs.has(sec.title) ? "" : controls.map((c) => this.controlRow(c)).join("");
1789
+ return `<div class="uiv-sec">${this.accordionTitle(sec.title)}${rows}</div>`;
1645
1790
  }).join("");
1646
1791
  }
1792
+ /** A collapsible section header. Clicking it hides/shows the section's controls. */
1793
+ accordionTitle(title) {
1794
+ const collapsed = this.collapsedSecs.has(title);
1795
+ return `<button class="uiv-sectitle uiv-acc${collapsed ? " collapsed" : ""}" data-sec="${escapeAttr(title)}"><span class="uiv-chev">${ICONS.chevron}</span>${title}</button>`;
1796
+ }
1647
1797
  numField(cssAttr, value, handle, changed, isSide, placeholder) {
1648
1798
  return `<div class="uiv-num${changed ? " changed" : ""}" data-css="${cssAttr}"><span class="uiv-scrub${isSide ? " txt" : ""}" title="Drag to change">${handle}</span><input type="number" value="${escapeAttr(value)}" placeholder="${escapeAttr(placeholder)}"></div>`;
1649
1799
  }
@@ -1745,7 +1895,8 @@ var Uivisor = class {
1745
1895
  }
1746
1896
  if (c.kind === "select") {
1747
1897
  const cur = this.selectCurrent(c.css);
1748
- const opts = c.options.map((o) => `<option value="${o}"${o === cur ? " selected" : ""}>${o}</option>`).join("");
1898
+ const optList = cur && !c.options.includes(cur) ? [cur, ...c.options] : c.options;
1899
+ const opts = optList.map((o) => `<option value="${o}"${o === cur ? " selected" : ""}>${o}</option>`).join("");
1749
1900
  return `<div class="uiv-ctl"><span class="clabel">${c.label}</span><div class="cfield"><select class="uiv-sel${this.isChanged([c.css]) ? " changed" : ""}" data-css="${c.css}">${opts}</select></div><span></span></div>`;
1750
1901
  }
1751
1902
  const val = toHexInput(this.liveVal(c.css));
@@ -1758,7 +1909,10 @@ var Uivisor = class {
1758
1909
  const cssList = (box.getAttribute("data-css") || "").split(",").filter(Boolean);
1759
1910
  const input = box.querySelector("input");
1760
1911
  const handle = box.querySelector(".uiv-scrub");
1761
- input.addEventListener("change", () => this.commitNumeric(cssList, input.value));
1912
+ input.addEventListener("change", () => {
1913
+ this.pushHistory();
1914
+ this.commitNumeric(cssList, input.value);
1915
+ });
1762
1916
  input.addEventListener("keydown", (e) => {
1763
1917
  if (e.key === "Enter") input.blur();
1764
1918
  });
@@ -1770,7 +1924,10 @@ var Uivisor = class {
1770
1924
  const input = box.querySelector("input");
1771
1925
  const unitSel = box.querySelector(".uiv-unit");
1772
1926
  const handle = box.querySelector(".uiv-scrub");
1773
- input.addEventListener("change", () => this.onDimInput(css, box));
1927
+ input.addEventListener("change", () => {
1928
+ this.pushHistory();
1929
+ this.onDimInput(css, box);
1930
+ });
1774
1931
  input.addEventListener("keydown", (e) => {
1775
1932
  if (e.key === "Enter") input.blur();
1776
1933
  });
@@ -1780,12 +1937,18 @@ var Uivisor = class {
1780
1937
  root.querySelectorAll(".uiv-color").forEach((node) => {
1781
1938
  const input = node;
1782
1939
  const css = input.getAttribute("data-css");
1783
- input.addEventListener("change", () => this.commitValue([css], input.value));
1940
+ input.addEventListener("change", () => {
1941
+ this.pushHistory();
1942
+ this.commitValue([css], input.value);
1943
+ });
1784
1944
  });
1785
1945
  root.querySelectorAll(".uiv-sel").forEach((node) => {
1786
1946
  const sel = node;
1787
1947
  const css = sel.getAttribute("data-css");
1788
- sel.addEventListener("change", () => this.commitValue([css], sel.value));
1948
+ sel.addEventListener("change", () => {
1949
+ this.pushHistory();
1950
+ this.commitValue([css], sel.value);
1951
+ });
1789
1952
  });
1790
1953
  root.querySelectorAll(".uiv-expand").forEach((node) => {
1791
1954
  const btn = node;
@@ -1796,6 +1959,15 @@ var Uivisor = class {
1796
1959
  this.renderBody();
1797
1960
  });
1798
1961
  });
1962
+ root.querySelectorAll(".uiv-acc").forEach((node) => {
1963
+ const btn = node;
1964
+ const sec = btn.getAttribute("data-sec");
1965
+ btn.addEventListener("click", () => {
1966
+ if (this.collapsedSecs.has(sec)) this.collapsedSecs.delete(sec);
1967
+ else this.collapsedSecs.add(sec);
1968
+ this.renderBody();
1969
+ });
1970
+ });
1799
1971
  root.querySelectorAll(".uiv-chip").forEach((node) => {
1800
1972
  const btn = node;
1801
1973
  const bp = btn.getAttribute("data-bp");
@@ -1811,7 +1983,6 @@ var Uivisor = class {
1811
1983
  this.toggleResponsive(true);
1812
1984
  } else {
1813
1985
  this.setFrameWidth(w);
1814
- this.retagSelected();
1815
1986
  this.renderBody();
1816
1987
  }
1817
1988
  });
@@ -1821,6 +1992,7 @@ var Uivisor = class {
1821
1992
  const target = btn.getAttribute("data-target");
1822
1993
  btn.addEventListener("click", () => {
1823
1994
  const st = this.st();
1995
+ if (st && st.record.target !== target) this.pushHistory();
1824
1996
  if (st) st.record.target = target;
1825
1997
  this.reapplyForTarget();
1826
1998
  this.renderBody();
@@ -1832,7 +2004,9 @@ var Uivisor = class {
1832
2004
  const st = this.st();
1833
2005
  if (!st) return;
1834
2006
  const name = input.value.trim().replace(/^\./, "").replace(/\s+/g, "-");
1835
- st.record.target = name ? `new:${name}` : "element";
2007
+ const next = name ? `new:${name}` : "element";
2008
+ if (st.record.target !== next) this.pushHistory();
2009
+ st.record.target = next;
1836
2010
  this.renderBody();
1837
2011
  });
1838
2012
  input.addEventListener("keydown", (e) => {
@@ -1849,7 +2023,12 @@ var Uivisor = class {
1849
2023
  handle.setPointerCapture(e.pointerId);
1850
2024
  } catch {
1851
2025
  }
2026
+ let pushed = false;
1852
2027
  const move = (ev) => {
2028
+ if (!pushed) {
2029
+ this.pushHistory();
2030
+ pushed = true;
2031
+ }
1853
2032
  const dx = ev.clientX - startX;
1854
2033
  let nv = start2 + Math.round(dx);
1855
2034
  if (ev.shiftKey) nv = Math.round(nv / 10) * 10;
@@ -1879,7 +2058,12 @@ var Uivisor = class {
1879
2058
  } catch {
1880
2059
  }
1881
2060
  const stepFor = (u) => u === "" ? 0.1 : u === "em" ? 0.01 : 1;
2061
+ let pushed = false;
1882
2062
  const move = (ev) => {
2063
+ if (!pushed) {
2064
+ this.pushHistory();
2065
+ pushed = true;
2066
+ }
1883
2067
  const u = unitSel.value;
1884
2068
  const step = stepFor(u) * (ev.shiftKey ? 10 : 1);
1885
2069
  const dec = step < 0.1 ? 2 : step < 1 ? 1 : 0;
@@ -1945,6 +2129,7 @@ var Uivisor = class {
1945
2129
  if (!el) return;
1946
2130
  const st = this.states.get(el);
1947
2131
  if (!st) return;
2132
+ if (st.record.changes.length) this.pushHistory();
1948
2133
  const sibs = this.siblingsOf(el);
1949
2134
  for (const css of st.applied) for (const e of sibs) removeOverride(e, css);
1950
2135
  st.applied.clear();
@@ -1953,6 +2138,7 @@ var Uivisor = class {
1953
2138
  this.renderBody();
1954
2139
  }
1955
2140
  clearAll() {
2141
+ if (this.states.size) this.pushHistory();
1956
2142
  for (const [el, st] of this.states) {
1957
2143
  const sibs = this.siblingsOf(el);
1958
2144
  for (const css of st.applied) for (const e of sibs) removeOverride(e, css);
@@ -1962,6 +2148,59 @@ var Uivisor = class {
1962
2148
  this.reposition();
1963
2149
  this.renderBody();
1964
2150
  }
2151
+ // ---- undo / redo ----
2152
+ /** Deep snapshot of all edit state (element refs kept; data JSON-cloned). */
2153
+ cloneSnap() {
2154
+ const clone = (v) => JSON.parse(JSON.stringify(v));
2155
+ return {
2156
+ selected: this.selected,
2157
+ entries: [...this.states.entries()].map(([el, st]) => ({
2158
+ el,
2159
+ record: clone(st.record),
2160
+ original: { ...st.original },
2161
+ dimUnit: { ...st.dimUnit }
2162
+ }))
2163
+ };
2164
+ }
2165
+ /** Record the current state so the next mutation can be undone. */
2166
+ pushHistory() {
2167
+ this.undoStack.push(this.cloneSnap());
2168
+ if (this.undoStack.length > 100) this.undoStack.shift();
2169
+ this.redoStack = [];
2170
+ }
2171
+ /** Rebuild all edit state from a snapshot and re-apply its live overrides. */
2172
+ applySnap(snap) {
2173
+ const clone = (v) => JSON.parse(JSON.stringify(v));
2174
+ for (const [el, st] of this.states) {
2175
+ const sibs = this.siblingsOf(el);
2176
+ for (const css of st.applied) for (const e of sibs) removeOverride(e, css);
2177
+ }
2178
+ this.states = /* @__PURE__ */ new Map();
2179
+ for (const ent of snap.entries) {
2180
+ const st = { record: clone(ent.record), original: { ...ent.original }, applied: /* @__PURE__ */ new Set(), dimUnit: { ...ent.dimUnit } };
2181
+ this.states.set(ent.el, st);
2182
+ const targets = st.record.target === "all" ? this.siblingsOf(ent.el) : [ent.el];
2183
+ for (const c of st.record.changes) {
2184
+ for (const e of targets) applyOverride(e, c.property, c.after.computed);
2185
+ st.applied.add(c.property);
2186
+ }
2187
+ }
2188
+ this.selected = snap.selected && this.states.has(snap.selected) ? snap.selected : null;
2189
+ this.reposition();
2190
+ this.renderBody();
2191
+ }
2192
+ undo() {
2193
+ if (!this.undoStack.length) return this.showToast("Nothing to undo");
2194
+ this.redoStack.push(this.cloneSnap());
2195
+ this.applySnap(this.undoStack.pop());
2196
+ this.showToast("Undo \u21A9");
2197
+ }
2198
+ redo() {
2199
+ if (!this.redoStack.length) return this.showToast("Nothing to redo");
2200
+ this.undoStack.push(this.cloneSnap());
2201
+ this.applySnap(this.redoStack.pop());
2202
+ this.showToast("Redo \u21AA");
2203
+ }
1965
2204
  async copy(text) {
1966
2205
  try {
1967
2206
  await navigator.clipboard.writeText(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uivisor",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Dev-only visual UI tweaker that turns mouse edits into a precise, breakpoint-aware prompt for your AI coding agent — without touching your source.",
6
6
  "license": "MIT",