uivisor 0.1.5 → 0.1.7

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 (2) hide show
  1. package/dist/overlay/index.js +268 -72
  2. package/package.json +1 -1
@@ -632,8 +632,12 @@ function nearestToken(ds, category, target) {
632
632
  const list = ds.byCategory[category];
633
633
  if (!list || !list.length) return null;
634
634
  if (target.value != null) {
635
- const norm = (s) => s.replace(/\s+/g, "").toLowerCase();
636
- const hit = list.find((t) => norm(t.value) === norm(target.value));
635
+ const norm = (s) => {
636
+ const t = s.trim();
637
+ return (rgbToHex(t) || t).replace(/\s+/g, "").toLowerCase();
638
+ };
639
+ const want = norm(target.value);
640
+ const hit = list.find((t) => norm(t.value) === want);
637
641
  if (hit) return { token: hit, exact: true };
638
642
  }
639
643
  if (target.px != null && list.some((t) => t.px != null)) {
@@ -1173,6 +1177,12 @@ var CSS = (
1173
1177
  .uiv-ctl select.uiv-tokensel:hover { border-color: #6366f1; color: #fff; }
1174
1178
  .uiv-ctl select.uiv-tokensel:focus { border-color: #818cf8; }
1175
1179
  .uiv-ctl select.uiv-tokensel.changed { border-color: #4ade80; color: #86efac; }
1180
+ .uiv-swatches { display: flex; flex-wrap: wrap; gap: 5px; }
1181
+ .uiv-swatch { width: 20px; height: 20px; border-radius: 5px; cursor: pointer;
1182
+ border: 1px solid rgba(255,255,255,0.18); padding: 0; outline: none;
1183
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15); }
1184
+ .uiv-swatch:hover { transform: scale(1.12); border-color: #fff; }
1185
+ .uiv-swatch.on { box-shadow: 0 0 0 2px #18181b, 0 0 0 3.5px #4ade80; border-color: #4ade80; }
1176
1186
 
1177
1187
  /* ---- current-styles readout ---- */
1178
1188
  .uiv-readout { display: flex; flex-direction: column; gap: 3px; }
@@ -1181,6 +1191,17 @@ var CSS = (
1181
1191
  .uiv-rk { color: #71717a; }
1182
1192
  .uiv-rv { color: #fff; word-break: break-all; display: flex; align-items: center; gap: 6px; }
1183
1193
  .uiv-rv.changed { color: #4ade80; } /* edited in uivisor \u2192 green */
1194
+
1195
+ /* control-row state: file (authored) \xB7 edited (this breakpoint) \xB7 auto (computed) */
1196
+ .uiv-ctl.st-file > .clabel { color: #e4e4e7; }
1197
+ .uiv-ctl.st-edited > .clabel { color: #4ade80; }
1198
+ .uiv-ctl.st-auto > .clabel { color: #6b7280; }
1199
+ .uiv-leg { display: flex; gap: 12px; padding: 8px 12px 2px; font-size: 9px;
1200
+ text-transform: uppercase; letter-spacing: .4px; }
1201
+ .uiv-lg { color: #e4e4e7; display: flex; align-items: center; gap: 4px; } /* file = white */
1202
+ .uiv-lg::before { content: ''; width: 7px; height: 7px; border-radius: 2px; background: currentColor; }
1203
+ .uiv-lg.edit { color: #4ade80; }
1204
+ .uiv-lg.auto { color: #6b7280; }
1184
1205
  .uiv-sw { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
1185
1206
  border: 1px solid rgba(255,255,255,0.2); flex: 0 0 auto; }
1186
1207
 
@@ -1282,6 +1303,15 @@ var CSS = (
1282
1303
  // src/overlay/index.ts
1283
1304
  var counter = 0;
1284
1305
  var round2 = (n) => Math.round(n * 100) / 100;
1306
+ var INHERITED_PROPS = /* @__PURE__ */ new Set([
1307
+ "font-size",
1308
+ "font-weight",
1309
+ "line-height",
1310
+ "letter-spacing",
1311
+ "color",
1312
+ "text-align",
1313
+ "font-family"
1314
+ ]);
1285
1315
  var Uivisor = class {
1286
1316
  constructor() {
1287
1317
  this.enabled = false;
@@ -1293,9 +1323,11 @@ var Uivisor = class {
1293
1323
  /** Undo / redo stacks of full edit-state snapshots. */
1294
1324
  this.undoStack = [];
1295
1325
  this.redoStack = [];
1296
- /** Cached live computed-style for the selected element (invalidated on reselect). */
1326
+ /** Cached live computed-style for the selected element (invalidated each render). */
1297
1327
  this._cs = null;
1298
1328
  this._csEl = null;
1329
+ /** Per-render memo of authored longhands per element (for the styles readout). */
1330
+ this._matched = null;
1299
1331
  /** Cached project breakpoint system (detected from CSS), refreshed until found. */
1300
1332
  this._bp = null;
1301
1333
  /** Cached project design system (detected from CSS variables), refreshed until found. */
@@ -1360,7 +1392,10 @@ var Uivisor = class {
1360
1392
  const winBp = currentBreakpoint(this.bpSystem()).name;
1361
1393
  if (winBp !== this.lastWinBp) {
1362
1394
  this.lastWinBp = winBp;
1363
- if (this.enabled && this.selected) this.renderBody();
1395
+ if (this.enabled && this.selected) {
1396
+ this.reapplyScope();
1397
+ this.renderBody();
1398
+ }
1364
1399
  }
1365
1400
  };
1366
1401
  this.reposition = () => {
@@ -1626,6 +1661,7 @@ var Uivisor = class {
1626
1661
  const up = () => {
1627
1662
  handle.removeEventListener("pointermove", move);
1628
1663
  handle.removeEventListener("pointerup", up);
1664
+ this.reapplyScope();
1629
1665
  this.renderBody();
1630
1666
  };
1631
1667
  handle.addEventListener("pointermove", move);
@@ -1661,6 +1697,7 @@ var Uivisor = class {
1661
1697
  dimUnit: {}
1662
1698
  });
1663
1699
  }
1700
+ if (el) this.reapplyScope();
1664
1701
  this.reposition();
1665
1702
  this.renderBody();
1666
1703
  }
@@ -1687,7 +1724,15 @@ var Uivisor = class {
1687
1724
  const el = this.selected;
1688
1725
  const st = this.st();
1689
1726
  if (!el || !st) return "";
1690
- return el.style.getPropertyValue(css) || this.computedVal(css) || st.original[css] || "";
1727
+ const scope = this.activeScope();
1728
+ const ch = st.record.changes.find((c) => c.property === css && c.breakpoint === scope.name);
1729
+ if (ch) {
1730
+ const v = ch.live ?? ch.after.computed;
1731
+ return v.includes("var(") ? this.computedVal(css) || v : v;
1732
+ }
1733
+ const inline = el.style.getPropertyValue(css);
1734
+ if (inline && !inline.includes("var(")) return inline;
1735
+ return this.computedVal(css) || st.original[css] || "";
1691
1736
  }
1692
1737
  liveNum(css) {
1693
1738
  const v = this.liveVal(css).trim();
@@ -1707,7 +1752,10 @@ var Uivisor = class {
1707
1752
  isChanged(cssList) {
1708
1753
  const st = this.st();
1709
1754
  if (!st) return false;
1710
- return cssList.some((c) => st.record.changes.some((ch) => ch.property === c));
1755
+ const scope = this.activeScope();
1756
+ return cssList.some(
1757
+ (c) => st.record.changes.some((ch) => ch.property === c && ch.breakpoint === scope.name)
1758
+ );
1711
1759
  }
1712
1760
  selectCurrent(css) {
1713
1761
  let v = this.liveVal(css).trim();
@@ -1752,16 +1800,27 @@ var Uivisor = class {
1752
1800
  }
1753
1801
  /** Re-apply recorded overrides after the target (all ↔ one) changes. */
1754
1802
  reapplyForTarget() {
1755
- const el = this.selected;
1756
- const st = this.st();
1757
- if (!el || !st) return;
1758
- const sibs = this.siblingsOf(el);
1759
- const targets = this.targetEls();
1760
- const props = new Set(st.record.changes.map((c) => c.property));
1761
- for (const css of props) {
1762
- for (const e of sibs) removeOverride(e, css);
1763
- const c = st.record.changes.find((ch) => ch.property === css);
1764
- if (c) for (const e of targets) applyOverride(e, css, c.after.computed);
1803
+ this.reapplyScope();
1804
+ }
1805
+ /**
1806
+ * Project the live preview for the ACTIVE breakpoint: strip every override we
1807
+ * applied, then re-apply ONLY the changes recorded for the current scope. This
1808
+ * is what makes per-breakpoint edits behave — set padding 20 at xl, 10 at md,
1809
+ * and each breakpoint shows (and previews) its own value instead of one global
1810
+ * inline override leaking across all of them.
1811
+ */
1812
+ reapplyScope() {
1813
+ const scope = this.activeScope();
1814
+ for (const [el, st] of this.states) {
1815
+ const sibs = this.siblingsOf(el);
1816
+ const targets = st.record.target === "all" ? sibs : [el];
1817
+ for (const css of st.applied) for (const e of sibs) removeOverride(e, css);
1818
+ st.applied = /* @__PURE__ */ new Set();
1819
+ for (const c of st.record.changes) {
1820
+ if (c.breakpoint !== scope.name) continue;
1821
+ for (const e of targets) applyOverride(e, c.property, c.live ?? c.after.computed);
1822
+ st.applied.add(c.property);
1823
+ }
1765
1824
  }
1766
1825
  this.reposition();
1767
1826
  }
@@ -1786,20 +1845,22 @@ var Uivisor = class {
1786
1845
  }
1787
1846
  this.renderBody();
1788
1847
  }
1789
- /** Apply a design-system token to a property: set its resolved value live and
1790
- * annotate the recorded change so the prompt asks the agent for the token. */
1848
+ /** Apply a design-system token to a property. The LIVE override is `var(--token)`
1849
+ * so a responsive token keeps adapting as you move across breakpoints; the
1850
+ * recorded value shows the token's value, tagged so the prompt asks for the token. */
1791
1851
  applyToken(css, token) {
1792
1852
  this.pushHistory();
1793
- this.liveSet([css], token.value);
1794
- this.recordProps([css]);
1853
+ const live = `var(${token.cssVar})`;
1854
+ this.liveSet([css], live);
1795
1855
  const st = this.st();
1796
1856
  if (!st) return;
1797
1857
  const scope = this.activeScope();
1798
- const ch = st.record.changes.find((c) => c.property === css && c.breakpoint === scope.name);
1799
- if (ch) {
1800
- ch.after.designToken = token.cssVar;
1801
- ch.after.token = st.record.styling.primaryMechanism === "tailwind" ? this.dsUtility(css, token) : null;
1802
- }
1858
+ st.applied.add(css);
1859
+ const change = buildChange(css, st.original[css], token.value, scope);
1860
+ change.live = live;
1861
+ change.after.designToken = token.cssVar;
1862
+ change.after.token = st.record.styling.primaryMechanism === "tailwind" ? this.dsUtility(css, token) : null;
1863
+ this.setChange(st.record, change);
1803
1864
  this.renderBody();
1804
1865
  }
1805
1866
  revertProps(cssList) {
@@ -1863,6 +1924,9 @@ var Uivisor = class {
1863
1924
  }
1864
1925
  // ---- rendering ----
1865
1926
  renderBody() {
1927
+ this._cs = null;
1928
+ this._csEl = null;
1929
+ this._matched = null;
1866
1930
  const body = this.q(".uiv-body");
1867
1931
  if (!this.selected) {
1868
1932
  body.innerHTML = `
@@ -1899,7 +1963,79 @@ var Uivisor = class {
1899
1963
  const cats = Object.keys(ds.byCategory).map((c) => `${ds.byCategory[c].length} ${c}`).join(" \xB7 ");
1900
1964
  return `<div class="uiv-dsbar" title="${escapeAttr(cats)}">\u25C6 Design system \xB7 ${ds.tokens.length} tokens detected</div>`;
1901
1965
  }
1902
- /** Read-only readout of the element's actual current styles so you don't guess. */
1966
+ /** Longhand properties an authoring rule (or non-uivisor inline) sets on `el`.
1967
+ * Reimplements getMatchedCSSRules over same-origin sheets (incl. matching
1968
+ * @media), so we can tell "set in the project's CSS" from "browser default". */
1969
+ matchedProps(el) {
1970
+ const cache = this._matched ||= /* @__PURE__ */ new Map();
1971
+ const cached = cache.get(el);
1972
+ if (cached) return cached;
1973
+ const out = /* @__PURE__ */ new Set();
1974
+ const he = el;
1975
+ const applied = el === this.selected ? this.st()?.applied ?? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set();
1976
+ if (he.style) for (let i = 0; i < he.style.length; i++) {
1977
+ const p = he.style[i];
1978
+ if (!applied.has(p)) out.add(p);
1979
+ }
1980
+ const win = el.ownerDocument.defaultView || window;
1981
+ const walk = (rules) => {
1982
+ for (let i = 0; i < rules.length; i++) {
1983
+ const r = rules[i];
1984
+ if (r.selectorText && r.style) {
1985
+ let m = false;
1986
+ try {
1987
+ m = el.matches(r.selectorText);
1988
+ } catch {
1989
+ m = false;
1990
+ }
1991
+ if (m) for (let j = 0; j < r.style.length; j++) out.add(r.style[j]);
1992
+ } else if (r.media && r.cssRules) {
1993
+ let ok = true;
1994
+ try {
1995
+ ok = win.matchMedia(r.media.mediaText).matches;
1996
+ } catch {
1997
+ ok = true;
1998
+ }
1999
+ if (ok) walk(r.cssRules);
2000
+ } else if (r.cssRules) {
2001
+ walk(r.cssRules);
2002
+ }
2003
+ }
2004
+ };
2005
+ const sheets = el.ownerDocument.styleSheets;
2006
+ for (let i = 0; i < sheets.length; i++) {
2007
+ try {
2008
+ walk(sheets[i].cssRules);
2009
+ } catch {
2010
+ }
2011
+ }
2012
+ cache.set(el, out);
2013
+ return out;
2014
+ }
2015
+ /** State class for an editable control's row: edited (green, at this breakpoint)
2016
+ * · file (white, authored in CSS) · auto (grey, browser-computed/default). */
2017
+ controlStateClass(props) {
2018
+ if (this.isChanged(props)) return " st-edited";
2019
+ const inherit = props.some((p) => INHERITED_PROPS.has(p));
2020
+ return this.isAuthored(props, inherit) ? " st-file" : " st-auto";
2021
+ }
2022
+ /** Is any of `props` authored in the project CSS? For inherited properties we
2023
+ * also walk ancestors (a body/parent font rule still counts as "from the file"). */
2024
+ isAuthored(props, inherit) {
2025
+ let node = this.selected;
2026
+ while (node) {
2027
+ const set = this.matchedProps(node);
2028
+ if (props.some((p) => set.has(p))) return true;
2029
+ node = inherit ? node.parentElement : null;
2030
+ }
2031
+ return false;
2032
+ }
2033
+ /**
2034
+ * Read-only readout of the element's ACTUAL current styles, three-state coloured:
2035
+ * white = authored in the project CSS · green = edited in uivisor ·
2036
+ * grey = browser-computed/auto (e.g. a width a flex item derived, a default).
2037
+ * Comprehensive + context-aware (flex/grid/position rows only when relevant).
2038
+ */
1903
2039
  currentStylesHtml() {
1904
2040
  const el = this.selected;
1905
2041
  if (!el) return "";
@@ -1915,47 +2051,70 @@ var Uivisor = class {
1915
2051
  const h = hex(v);
1916
2052
  return /^#|rgb/.test(h) ? `<span class="uiv-sw" style="background:${h}"></span>${h}` : h;
1917
2053
  };
1918
- const box = (prefix) => {
1919
- const t = g(`${prefix}-top`);
1920
- const r = g(`${prefix}-right`);
1921
- const b = g(`${prefix}-bottom`);
1922
- const l = g(`${prefix}-left`);
2054
+ const sides = (parts) => {
2055
+ const [t, r, b, l] = parts.map(g);
1923
2056
  if (t === r && r === b && b === l) return t;
1924
2057
  if (t === b && r === l) return `${t} ${r}`;
1925
2058
  return `${t} ${r} ${b} ${l}`;
1926
2059
  };
1927
- const changedSet = new Set(this.st()?.record.changes.map((c) => c.property) ?? []);
1928
- const changed = (props) => props.some((p) => changedSet.has(p));
1929
- const px4 = (pre) => [`${pre}-top`, `${pre}-right`, `${pre}-bottom`, `${pre}-left`];
2060
+ const px4 = (pre, suf = "") => [`${pre}-top${suf}`, `${pre}-right${suf}`, `${pre}-bottom${suf}`, `${pre}-left${suf}`];
2061
+ const clip = (s, n = 30) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
1930
2062
  const rows = [];
1931
- const add = (k, v, props = []) => {
1932
- if (v) rows.push({ k, v, edited: changed(props) });
2063
+ const add = (k, v, _props = [], _inherit = false) => {
2064
+ if (v !== "" && v != null) rows.push({ k, v });
1933
2065
  };
1934
- add("display", g("display"));
1935
- add("size", `${Math.round(parseFloat(g("width")) || 0)} \xD7 ${Math.round(parseFloat(g("height")) || 0)}`);
1936
- const pad = box("padding");
1937
- if (pad && pad !== "0px") add("padding", pad, px4("padding"));
1938
- const mar = box("margin");
1939
- if (mar && mar !== "0px") add("margin", mar, px4("margin"));
1940
- if (/flex|grid/.test(g("display"))) {
2066
+ const disp = g("display");
2067
+ add("display", disp, ["display"]);
2068
+ const pos = g("position");
2069
+ if (pos && pos !== "static") {
2070
+ add("position", pos, ["position"]);
2071
+ const inset = ["top", "right", "bottom", "left"].filter((s) => g(s) !== "auto").map((s) => `${s[0]} ${g(s)}`).join(" ");
2072
+ if (inset) add("inset", inset, ["top", "right", "bottom", "left"]);
2073
+ }
2074
+ add("width", `${Math.round(parseFloat(g("width")) || 0)}px`, ["width"]);
2075
+ add("height", `${Math.round(parseFloat(g("height")) || 0)}px`, ["height"]);
2076
+ const maxw = g("max-width");
2077
+ if (maxw && maxw !== "none") add("max-w", maxw, ["max-width"]);
2078
+ const minh = g("min-height");
2079
+ if (minh && minh !== "0px" && minh !== "auto") add("min-h", minh, ["min-height"]);
2080
+ if (/flex|grid/.test(disp)) {
2081
+ if (disp.includes("flex")) add("direction", g("flex-direction"), ["flex-direction"]);
2082
+ add("justify", g("justify-content"), ["justify-content"]);
2083
+ add("align", g("align-items"), ["align-items"]);
2084
+ if (disp.includes("flex")) {
2085
+ const w = g("flex-wrap");
2086
+ if (w && w !== "nowrap") add("wrap", w, ["flex-wrap"]);
2087
+ }
2088
+ if (disp.includes("grid")) {
2089
+ const c = g("grid-template-columns");
2090
+ if (c && c !== "none") add("grid-cols", clip(c), ["grid-template-columns"]);
2091
+ }
1941
2092
  const gap = g("gap");
1942
- if (gap && gap !== "normal" && gap !== "0px") add("gap", gap, ["gap"]);
2093
+ if (gap && gap !== "normal") add("gap", gap, ["gap", "row-gap", "column-gap"]);
1943
2094
  }
1944
- add("font", `${g("font-size")} \xB7 ${g("font-weight")} \xB7 lh ${g("line-height")}`, [
1945
- "font-size",
1946
- "font-weight",
1947
- "line-height"
1948
- ]);
1949
- const ls = g("letter-spacing");
1950
- if (ls && ls !== "normal") add("tracking", ls, ["letter-spacing"]);
1951
- add("color", swatch(g("color")), ["color"]);
1952
- const bg = g("background-color");
1953
- if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent")
1954
- add("background", swatch(bg), ["background-color"]);
2095
+ let parentDisp = "";
2096
+ try {
2097
+ if (el.parentElement) parentDisp = getComputedStyle(el.parentElement).display;
2098
+ } catch {
2099
+ }
2100
+ if (/flex|grid/.test(parentDisp)) {
2101
+ add("flex", `${g("flex-grow")} ${g("flex-shrink")} ${g("flex-basis")}`, ["flex-grow", "flex-shrink", "flex-basis"]);
2102
+ const as = g("align-self");
2103
+ if (as && as !== "auto" && as !== "normal") add("self", as, ["align-self"]);
2104
+ }
2105
+ const pad = sides(px4("padding"));
2106
+ if (pad && pad !== "0px") add("padding", pad, px4("padding"));
2107
+ const mar = sides(px4("margin"));
2108
+ if (mar && mar !== "0px") add("margin", mar, px4("margin"));
1955
2109
  const bw = g("border-top-width");
1956
2110
  if (bw && parseFloat(bw) > 0)
1957
- add("border", `${bw} ${g("border-top-style")} ${hex(g("border-top-color"))}`, px4("border").map((p) => `${p}-width`));
1958
- const br = g("border-radius");
2111
+ add("border", `${bw} ${g("border-top-style")} ${swatch(g("border-top-color"))}`, px4("border", "-width"));
2112
+ const br = sides([
2113
+ "border-top-left-radius",
2114
+ "border-top-right-radius",
2115
+ "border-bottom-right-radius",
2116
+ "border-bottom-left-radius"
2117
+ ]);
1959
2118
  if (br && br !== "0px")
1960
2119
  add("radius", br, [
1961
2120
  "border-radius",
@@ -1964,11 +2123,32 @@ var Uivisor = class {
1964
2123
  "border-bottom-right-radius",
1965
2124
  "border-bottom-left-radius"
1966
2125
  ]);
1967
- if (g("box-shadow") !== "none" && g("box-shadow")) add("shadow", "yes");
2126
+ if (this.context(el).hasText) {
2127
+ add("font-size", g("font-size"), ["font-size"], true);
2128
+ add("weight", g("font-weight"), ["font-weight"], true);
2129
+ add("line", g("line-height"), ["line-height"], true);
2130
+ const ls = g("letter-spacing");
2131
+ if (ls && ls !== "normal") add("tracking", ls, ["letter-spacing"], true);
2132
+ add("color", swatch(g("color")), ["color"], true);
2133
+ const ta = g("text-align");
2134
+ if (ta && ta !== "start" && ta !== "left") add("text-align", ta, ["text-align"], true);
2135
+ }
2136
+ const bg = g("background-color");
2137
+ if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") add("background", swatch(bg), ["background-color"]);
2138
+ const bgi = g("background-image");
2139
+ if (bgi && bgi !== "none") add("bg-image", clip(bgi, 26), ["background-image"]);
2140
+ const sh = g("box-shadow");
2141
+ if (sh && sh !== "none") add("shadow", clip(sh, 26), ["box-shadow"]);
1968
2142
  const op = g("opacity");
1969
- if (op && parseFloat(op) < 1) add("opacity", op);
2143
+ if (op && parseFloat(op) < 1) add("opacity", op, ["opacity"]);
2144
+ const ov = g("overflow");
2145
+ if (ov && ov !== "visible") add("overflow", ov, ["overflow", "overflow-x", "overflow-y"]);
2146
+ const z = g("z-index");
2147
+ if (z && z !== "auto") add("z-index", z, ["z-index"]);
2148
+ const tr = g("transform");
2149
+ if (tr && tr !== "none") add("transform", clip(tr, 22), ["transform"]);
1970
2150
  const collapsed = this.collapsedSecs.has("Current styles");
1971
- 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("");
2151
+ const items = collapsed ? "" : rows.map((r) => `<div class="uiv-rrow"><span class="uiv-rk">${r.k}</span><span class="uiv-rv">${r.v}</span></div>`).join("");
1972
2152
  return `<div class="uiv-sec">${this.accordionTitle("Current styles")}<div class="uiv-readout">${items}</div></div>`;
1973
2153
  }
1974
2154
  /** Breakpoint scope switcher: shows the PROJECT's breakpoints + the live window one. */
@@ -2022,12 +2202,14 @@ var Uivisor = class {
2022
2202
  return true;
2023
2203
  }
2024
2204
  controlsHtml(ctx) {
2025
- return SECTIONS.map((sec) => {
2205
+ const legend = `<div class="uiv-leg"><span class="uiv-lg">file</span><span class="uiv-lg edit">edited</span><span class="uiv-lg auto">auto</span></div>`;
2206
+ const secs = SECTIONS.map((sec) => {
2026
2207
  const controls = sec.controls.filter((c) => this.relevant(c, ctx));
2027
2208
  if (!controls.length) return "";
2028
2209
  const rows = this.collapsedSecs.has(sec.title) ? "" : controls.map((c) => this.controlRow(c)).join("");
2029
2210
  return `<div class="uiv-sec">${this.accordionTitle(sec.title)}${rows}</div>`;
2030
2211
  }).join("");
2212
+ return legend + secs;
2031
2213
  }
2032
2214
  /** A collapsible section header. Clicking it hides/shows the section's controls. */
2033
2215
  accordionTitle(title) {
@@ -2102,7 +2284,7 @@ var Uivisor = class {
2102
2284
  const d = this.dimDisplay(c);
2103
2285
  const changed = this.isChanged([c.css]);
2104
2286
  const units = c.units.map((u) => `<option value="${u}"${u === d.unit ? " selected" : ""}>${UNIT_LABELS[u] ?? u}</option>`).join("");
2105
- return `<div class="uiv-ctl"><span class="clabel">${c.label}</span><div class="cfield"><div class="uiv-num uiv-dim${changed ? " changed" : ""}" data-css="${c.css}"><span class="uiv-scrub" title="Drag to change">${c.icon}</span><input type="number" step="any" value="${escapeAttr(d.num)}" placeholder="${escapeAttr(d.placeholder)}"><select class="uiv-unit" title="Unit">${units}</select></div></div><span></span></div>`;
2287
+ return `<div class="uiv-ctl${this.controlStateClass([c.css])}"><span class="clabel">${c.label}</span><div class="cfield"><div class="uiv-num uiv-dim${changed ? " changed" : ""}" data-css="${c.css}"><span class="uiv-scrub" title="Drag to change">${c.icon}</span><input type="number" step="any" value="${escapeAttr(d.num)}" placeholder="${escapeAttr(d.placeholder)}"><select class="uiv-unit" title="Unit">${units}</select></div></div><span></span></div>`;
2106
2288
  }
2107
2289
  /** A design-token picker row for a property, shown only when the project exposes
2108
2290
  * tokens for that category. Picking a token applies its value + tags the prompt. */
@@ -2117,6 +2299,13 @@ var Uivisor = class {
2117
2299
  const edited = this.st()?.record.changes.some(
2118
2300
  (c) => c.property === css && c.after.designToken
2119
2301
  );
2302
+ if (cat === "color") {
2303
+ const swatches = list.map((t) => {
2304
+ const on = near?.exact && near.token.cssVar === t.cssVar;
2305
+ return `<button class="uiv-swatch${on ? " on" : ""}" data-css="${css}" data-var="${escapeAttr(t.cssVar)}" title="${escapeAttr(`${t.name} \xB7 ${t.value}`)}" style="background:${escapeAttr(t.value)}"></button>`;
2306
+ }).join("");
2307
+ return `<div class="uiv-ctl"><span class="clabel uiv-tlabel">${label}</span><div class="cfield uiv-swatches">${swatches}</div><span></span></div>`;
2308
+ }
2120
2309
  const head = `<option value="">${near && !near.exact ? `\u2248 ${escapeHtml(near.token.name)} \xB7 pick token` : "\u2014 pick token \u2014"}</option>`;
2121
2310
  const opts = list.map(
2122
2311
  (t) => `<option value="${escapeAttr(t.cssVar)}"${near?.exact && near.token.cssVar === t.cssVar ? " selected" : ""}>${escapeHtml(`${t.name} \xB7 ${t.value}`)}</option>`
@@ -2129,7 +2318,7 @@ var Uivisor = class {
2129
2318
  const info = this.numInfo(cssList);
2130
2319
  const changed = this.isChanged(cssList);
2131
2320
  const open = this.expanded.has(c.key);
2132
- let html = `<div class="uiv-ctl"><span class="clabel">${c.label}</span><div class="cfield">${this.numField(cssList.join(","), info.mixed ? "" : info.value, c.icon, changed, false, info.mixed ? "Mixed" : "\u2014")}</div><button class="uiv-expand${open ? " on" : ""}" data-key="${c.key}" title="Edit each side individually">${open ? ICONS.collapse : ICONS.expand}</button></div>`;
2321
+ let html = `<div class="uiv-ctl${this.controlStateClass(cssList)}"><span class="clabel">${c.label}</span><div class="cfield">${this.numField(cssList.join(","), info.mixed ? "" : info.value, c.icon, changed, false, info.mixed ? "Mixed" : "\u2014")}</div><button class="uiv-expand${open ? " on" : ""}" data-key="${c.key}" title="Edit each side individually">${open ? ICONS.collapse : ICONS.expand}</button></div>`;
2133
2322
  if (open) {
2134
2323
  html += `<div class="uiv-sides">` + c.sides.map((s) => {
2135
2324
  const v = this.liveNum(s.css);
@@ -2147,7 +2336,7 @@ var Uivisor = class {
2147
2336
  }
2148
2337
  if (c.kind === "len") {
2149
2338
  const v = this.liveNum(c.css);
2150
- return `<div class="uiv-ctl"><span class="clabel">${c.label}</span><div class="cfield">${this.numField(c.css, v == null ? "" : String(round2(v)), c.icon, this.isChanged([c.css]), false, "\u2014")}</div><span></span></div>` + this.tokenRowHtml(c.css, "Token");
2339
+ return `<div class="uiv-ctl${this.controlStateClass([c.css])}"><span class="clabel">${c.label}</span><div class="cfield">${this.numField(c.css, v == null ? "" : String(round2(v)), c.icon, this.isChanged([c.css]), false, "\u2014")}</div><span></span></div>` + this.tokenRowHtml(c.css, "Token");
2151
2340
  }
2152
2341
  if (c.kind === "dim") {
2153
2342
  return this.dimField(c);
@@ -2156,10 +2345,10 @@ var Uivisor = class {
2156
2345
  const cur = this.selectCurrent(c.css);
2157
2346
  const optList = cur && !c.options.includes(cur) ? [cur, ...c.options] : c.options;
2158
2347
  const opts = optList.map((o) => `<option value="${o}"${o === cur ? " selected" : ""}>${o}</option>`).join("");
2159
- 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>`;
2348
+ return `<div class="uiv-ctl${this.controlStateClass([c.css])}"><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>`;
2160
2349
  }
2161
2350
  const val = toHexInput(this.liveVal(c.css));
2162
- return `<div class="uiv-ctl"><span class="clabel">${c.label}</span><div class="cfield"><input type="color" class="uiv-color${this.isChanged([c.css]) ? " changed" : ""}" data-css="${c.css}" value="${val}"></div><span></span></div>` + this.tokenRowHtml(c.css, "Token");
2351
+ return `<div class="uiv-ctl${this.controlStateClass([c.css])}"><span class="clabel">${c.label}</span><div class="cfield"><input type="color" class="uiv-color${this.isChanged([c.css]) ? " changed" : ""}" data-css="${c.css}" value="${val}"></div><span></span></div>` + this.tokenRowHtml(c.css, "Token");
2163
2352
  }
2164
2353
  bindControls() {
2165
2354
  const root = this.root;
@@ -2218,6 +2407,15 @@ var Uivisor = class {
2218
2407
  if (token) this.applyToken(css, token);
2219
2408
  });
2220
2409
  });
2410
+ root.querySelectorAll(".uiv-swatch").forEach((node) => {
2411
+ const btn = node;
2412
+ const css = btn.getAttribute("data-css");
2413
+ const cssVar = btn.getAttribute("data-var");
2414
+ btn.addEventListener("click", () => {
2415
+ const token = this.designSystem().tokens.find((t) => t.cssVar === cssVar);
2416
+ if (token) this.applyToken(css, token);
2417
+ });
2418
+ });
2221
2419
  root.querySelectorAll(".uiv-expand").forEach((node) => {
2222
2420
  const btn = node;
2223
2421
  const key = btn.getAttribute("data-key");
@@ -2251,6 +2449,7 @@ var Uivisor = class {
2251
2449
  this.toggleResponsive(true);
2252
2450
  } else {
2253
2451
  this.setFrameWidth(w);
2452
+ this.reapplyScope();
2254
2453
  this.renderBody();
2255
2454
  }
2256
2455
  });
@@ -2364,7 +2563,8 @@ var Uivisor = class {
2364
2563
  const loc = id.source.confidence !== "none" ? `${id.source.file}:${id.source.line}` : id.componentName || "";
2365
2564
  const chgs = collapseChanges(r.changes).map((c) => {
2366
2565
  const bp = c.breakpoint === "base" ? "" : `<span class="bp">${c.breakpoint}:</span> `;
2367
- const tok = c.after.token ? ` <span class="tok">${escapeHtml(c.after.token)}</span>` : "";
2566
+ const tokLabel = c.after.token || (c.after.designToken ? `var(${c.after.designToken})` : "");
2567
+ const tok = tokLabel ? ` <span class="tok">${escapeHtml(tokLabel)}</span>` : "";
2368
2568
  return `<div class="uiv-jchg">${bp}${c.property}: ${c.before.computed} \u2192 ${c.after.computed}${tok}</div>`;
2369
2569
  }).join("");
2370
2570
  return `<div class="uiv-jitem"><div class="jhead"><span class="jel">&lt;${id.tagName}&gt;</span><span class="jloc">${escapeHtml(loc)}</span></div>${chgs}</div>`;
@@ -2447,13 +2647,9 @@ var Uivisor = class {
2447
2647
  for (const ent of snap.entries) {
2448
2648
  const st = { record: clone(ent.record), original: { ...ent.original }, applied: /* @__PURE__ */ new Set(), dimUnit: { ...ent.dimUnit } };
2449
2649
  this.states.set(ent.el, st);
2450
- const targets = st.record.target === "all" ? this.siblingsOf(ent.el) : [ent.el];
2451
- for (const c of st.record.changes) {
2452
- for (const e of targets) applyOverride(e, c.property, c.after.computed);
2453
- st.applied.add(c.property);
2454
- }
2455
2650
  }
2456
2651
  this.selected = snap.selected && this.states.has(snap.selected) ? snap.selected : null;
2652
+ this.reapplyScope();
2457
2653
  this.reposition();
2458
2654
  this.renderBody();
2459
2655
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uivisor",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",