uivisor 0.1.5 → 0.1.6

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 +210 -50
  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,13 @@ 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
+ .uiv-rv.auto { color: #6b7280; } /* browser-computed / default \u2192 grey */
1195
+ .uiv-leg { display: flex; gap: 10px; padding: 1px 0 6px; font-size: 9px;
1196
+ text-transform: uppercase; letter-spacing: .4px; }
1197
+ .uiv-lg { color: #e4e4e7; display: flex; align-items: center; gap: 4px; } /* file = white */
1198
+ .uiv-lg::before { content: ''; width: 7px; height: 7px; border-radius: 2px; background: currentColor; }
1199
+ .uiv-lg.edit { color: #4ade80; }
1200
+ .uiv-lg.auto { color: #6b7280; }
1184
1201
  .uiv-sw { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
1185
1202
  border: 1px solid rgba(255,255,255,0.2); flex: 0 0 auto; }
1186
1203
 
@@ -1293,9 +1310,11 @@ var Uivisor = class {
1293
1310
  /** Undo / redo stacks of full edit-state snapshots. */
1294
1311
  this.undoStack = [];
1295
1312
  this.redoStack = [];
1296
- /** Cached live computed-style for the selected element (invalidated on reselect). */
1313
+ /** Cached live computed-style for the selected element (invalidated each render). */
1297
1314
  this._cs = null;
1298
1315
  this._csEl = null;
1316
+ /** Per-render memo of authored longhands per element (for the styles readout). */
1317
+ this._matched = null;
1299
1318
  /** Cached project breakpoint system (detected from CSS), refreshed until found. */
1300
1319
  this._bp = null;
1301
1320
  /** Cached project design system (detected from CSS variables), refreshed until found. */
@@ -1687,7 +1706,9 @@ var Uivisor = class {
1687
1706
  const el = this.selected;
1688
1707
  const st = this.st();
1689
1708
  if (!el || !st) return "";
1690
- return el.style.getPropertyValue(css) || this.computedVal(css) || st.original[css] || "";
1709
+ const inline = el.style.getPropertyValue(css);
1710
+ if (inline && !inline.includes("var(")) return inline;
1711
+ return this.computedVal(css) || st.original[css] || "";
1691
1712
  }
1692
1713
  liveNum(css) {
1693
1714
  const v = this.liveVal(css).trim();
@@ -1761,7 +1782,7 @@ var Uivisor = class {
1761
1782
  for (const css of props) {
1762
1783
  for (const e of sibs) removeOverride(e, css);
1763
1784
  const c = st.record.changes.find((ch) => ch.property === css);
1764
- if (c) for (const e of targets) applyOverride(e, css, c.after.computed);
1785
+ if (c) for (const e of targets) applyOverride(e, css, c.live ?? c.after.computed);
1765
1786
  }
1766
1787
  this.reposition();
1767
1788
  }
@@ -1786,20 +1807,22 @@ var Uivisor = class {
1786
1807
  }
1787
1808
  this.renderBody();
1788
1809
  }
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. */
1810
+ /** Apply a design-system token to a property. The LIVE override is `var(--token)`
1811
+ * so a responsive token keeps adapting as you move across breakpoints; the
1812
+ * recorded value shows the token's value, tagged so the prompt asks for the token. */
1791
1813
  applyToken(css, token) {
1792
1814
  this.pushHistory();
1793
- this.liveSet([css], token.value);
1794
- this.recordProps([css]);
1815
+ const live = `var(${token.cssVar})`;
1816
+ this.liveSet([css], live);
1795
1817
  const st = this.st();
1796
1818
  if (!st) return;
1797
1819
  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
- }
1820
+ st.applied.add(css);
1821
+ const change = buildChange(css, st.original[css], token.value, scope);
1822
+ change.live = live;
1823
+ change.after.designToken = token.cssVar;
1824
+ change.after.token = st.record.styling.primaryMechanism === "tailwind" ? this.dsUtility(css, token) : null;
1825
+ this.setChange(st.record, change);
1803
1826
  this.renderBody();
1804
1827
  }
1805
1828
  revertProps(cssList) {
@@ -1863,6 +1886,9 @@ var Uivisor = class {
1863
1886
  }
1864
1887
  // ---- rendering ----
1865
1888
  renderBody() {
1889
+ this._cs = null;
1890
+ this._csEl = null;
1891
+ this._matched = null;
1866
1892
  const body = this.q(".uiv-body");
1867
1893
  if (!this.selected) {
1868
1894
  body.innerHTML = `
@@ -1899,7 +1925,72 @@ var Uivisor = class {
1899
1925
  const cats = Object.keys(ds.byCategory).map((c) => `${ds.byCategory[c].length} ${c}`).join(" \xB7 ");
1900
1926
  return `<div class="uiv-dsbar" title="${escapeAttr(cats)}">\u25C6 Design system \xB7 ${ds.tokens.length} tokens detected</div>`;
1901
1927
  }
1902
- /** Read-only readout of the element's actual current styles so you don't guess. */
1928
+ /** Longhand properties an authoring rule (or non-uivisor inline) sets on `el`.
1929
+ * Reimplements getMatchedCSSRules over same-origin sheets (incl. matching
1930
+ * @media), so we can tell "set in the project's CSS" from "browser default". */
1931
+ matchedProps(el) {
1932
+ const cache = this._matched ||= /* @__PURE__ */ new Map();
1933
+ const cached = cache.get(el);
1934
+ if (cached) return cached;
1935
+ const out = /* @__PURE__ */ new Set();
1936
+ const he = el;
1937
+ const applied = el === this.selected ? this.st()?.applied ?? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set();
1938
+ if (he.style) for (let i = 0; i < he.style.length; i++) {
1939
+ const p = he.style[i];
1940
+ if (!applied.has(p)) out.add(p);
1941
+ }
1942
+ const win = el.ownerDocument.defaultView || window;
1943
+ const walk = (rules) => {
1944
+ for (let i = 0; i < rules.length; i++) {
1945
+ const r = rules[i];
1946
+ if (r.selectorText && r.style) {
1947
+ let m = false;
1948
+ try {
1949
+ m = el.matches(r.selectorText);
1950
+ } catch {
1951
+ m = false;
1952
+ }
1953
+ if (m) for (let j = 0; j < r.style.length; j++) out.add(r.style[j]);
1954
+ } else if (r.media && r.cssRules) {
1955
+ let ok = true;
1956
+ try {
1957
+ ok = win.matchMedia(r.media.mediaText).matches;
1958
+ } catch {
1959
+ ok = true;
1960
+ }
1961
+ if (ok) walk(r.cssRules);
1962
+ } else if (r.cssRules) {
1963
+ walk(r.cssRules);
1964
+ }
1965
+ }
1966
+ };
1967
+ const sheets = el.ownerDocument.styleSheets;
1968
+ for (let i = 0; i < sheets.length; i++) {
1969
+ try {
1970
+ walk(sheets[i].cssRules);
1971
+ } catch {
1972
+ }
1973
+ }
1974
+ cache.set(el, out);
1975
+ return out;
1976
+ }
1977
+ /** Is any of `props` authored in the project CSS? For inherited properties we
1978
+ * also walk ancestors (a body/parent font rule still counts as "from the file"). */
1979
+ isAuthored(props, inherit) {
1980
+ let node = this.selected;
1981
+ while (node) {
1982
+ const set = this.matchedProps(node);
1983
+ if (props.some((p) => set.has(p))) return true;
1984
+ node = inherit ? node.parentElement : null;
1985
+ }
1986
+ return false;
1987
+ }
1988
+ /**
1989
+ * Read-only readout of the element's ACTUAL current styles, three-state coloured:
1990
+ * white = authored in the project CSS · green = edited in uivisor ·
1991
+ * grey = browser-computed/auto (e.g. a width a flex item derived, a default).
1992
+ * Comprehensive + context-aware (flex/grid/position rows only when relevant).
1993
+ */
1903
1994
  currentStylesHtml() {
1904
1995
  const el = this.selected;
1905
1996
  if (!el) return "";
@@ -1915,47 +2006,75 @@ var Uivisor = class {
1915
2006
  const h = hex(v);
1916
2007
  return /^#|rgb/.test(h) ? `<span class="uiv-sw" style="background:${h}"></span>${h}` : h;
1917
2008
  };
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`);
2009
+ const sides = (parts) => {
2010
+ const [t, r, b, l] = parts.map(g);
1923
2011
  if (t === r && r === b && b === l) return t;
1924
2012
  if (t === b && r === l) return `${t} ${r}`;
1925
2013
  return `${t} ${r} ${b} ${l}`;
1926
2014
  };
2015
+ const px4 = (pre, suf = "") => [`${pre}-top${suf}`, `${pre}-right${suf}`, `${pre}-bottom${suf}`, `${pre}-left${suf}`];
2016
+ const clip = (s, n = 30) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
1927
2017
  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`];
2018
+ const stateCls = (props, inherit = false) => {
2019
+ if (props.some((p) => changedSet.has(p))) return " changed";
2020
+ return this.isAuthored(props, inherit) ? "" : " auto";
2021
+ };
1930
2022
  const rows = [];
1931
- const add = (k, v, props = []) => {
1932
- if (v) rows.push({ k, v, edited: changed(props) });
2023
+ const add = (k, v, props, inherit = false) => {
2024
+ if (v !== "" && v != null) rows.push({ k, v, cls: stateCls(props, inherit) });
1933
2025
  };
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"))) {
2026
+ const disp = g("display");
2027
+ add("display", disp, ["display"]);
2028
+ const pos = g("position");
2029
+ if (pos && pos !== "static") {
2030
+ add("position", pos, ["position"]);
2031
+ const inset = ["top", "right", "bottom", "left"].filter((s) => g(s) !== "auto").map((s) => `${s[0]} ${g(s)}`).join(" ");
2032
+ if (inset) add("inset", inset, ["top", "right", "bottom", "left"]);
2033
+ }
2034
+ add("width", `${Math.round(parseFloat(g("width")) || 0)}px`, ["width"]);
2035
+ add("height", `${Math.round(parseFloat(g("height")) || 0)}px`, ["height"]);
2036
+ const maxw = g("max-width");
2037
+ if (maxw && maxw !== "none") add("max-w", maxw, ["max-width"]);
2038
+ const minh = g("min-height");
2039
+ if (minh && minh !== "0px" && minh !== "auto") add("min-h", minh, ["min-height"]);
2040
+ if (/flex|grid/.test(disp)) {
2041
+ if (disp.includes("flex")) add("direction", g("flex-direction"), ["flex-direction"]);
2042
+ add("justify", g("justify-content"), ["justify-content"]);
2043
+ add("align", g("align-items"), ["align-items"]);
2044
+ if (disp.includes("flex")) {
2045
+ const w = g("flex-wrap");
2046
+ if (w && w !== "nowrap") add("wrap", w, ["flex-wrap"]);
2047
+ }
2048
+ if (disp.includes("grid")) {
2049
+ const c = g("grid-template-columns");
2050
+ if (c && c !== "none") add("grid-cols", clip(c), ["grid-template-columns"]);
2051
+ }
1941
2052
  const gap = g("gap");
1942
- if (gap && gap !== "normal" && gap !== "0px") add("gap", gap, ["gap"]);
2053
+ if (gap && gap !== "normal") add("gap", gap, ["gap", "row-gap", "column-gap"]);
1943
2054
  }
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"]);
2055
+ let parentDisp = "";
2056
+ try {
2057
+ if (el.parentElement) parentDisp = getComputedStyle(el.parentElement).display;
2058
+ } catch {
2059
+ }
2060
+ if (/flex|grid/.test(parentDisp)) {
2061
+ add("flex", `${g("flex-grow")} ${g("flex-shrink")} ${g("flex-basis")}`, ["flex-grow", "flex-shrink", "flex-basis"]);
2062
+ const as = g("align-self");
2063
+ if (as && as !== "auto" && as !== "normal") add("self", as, ["align-self"]);
2064
+ }
2065
+ const pad = sides(px4("padding"));
2066
+ if (pad && pad !== "0px") add("padding", pad, px4("padding"));
2067
+ const mar = sides(px4("margin"));
2068
+ if (mar && mar !== "0px") add("margin", mar, px4("margin"));
1955
2069
  const bw = g("border-top-width");
1956
2070
  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");
2071
+ add("border", `${bw} ${g("border-top-style")} ${swatch(g("border-top-color"))}`, px4("border", "-width"));
2072
+ const br = sides([
2073
+ "border-top-left-radius",
2074
+ "border-top-right-radius",
2075
+ "border-bottom-right-radius",
2076
+ "border-bottom-left-radius"
2077
+ ]);
1959
2078
  if (br && br !== "0px")
1960
2079
  add("radius", br, [
1961
2080
  "border-radius",
@@ -1964,11 +2083,35 @@ var Uivisor = class {
1964
2083
  "border-bottom-right-radius",
1965
2084
  "border-bottom-left-radius"
1966
2085
  ]);
1967
- if (g("box-shadow") !== "none" && g("box-shadow")) add("shadow", "yes");
2086
+ if (this.context(el).hasText) {
2087
+ add("font-size", g("font-size"), ["font-size"], true);
2088
+ add("weight", g("font-weight"), ["font-weight"], true);
2089
+ add("line", g("line-height"), ["line-height"], true);
2090
+ const ls = g("letter-spacing");
2091
+ if (ls && ls !== "normal") add("tracking", ls, ["letter-spacing"], true);
2092
+ add("color", swatch(g("color")), ["color"], true);
2093
+ const ta = g("text-align");
2094
+ if (ta && ta !== "start" && ta !== "left") add("text-align", ta, ["text-align"], true);
2095
+ }
2096
+ const bg = g("background-color");
2097
+ if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") add("background", swatch(bg), ["background-color"]);
2098
+ const bgi = g("background-image");
2099
+ if (bgi && bgi !== "none") add("bg-image", clip(bgi, 26), ["background-image"]);
2100
+ const sh = g("box-shadow");
2101
+ if (sh && sh !== "none") add("shadow", clip(sh, 26), ["box-shadow"]);
1968
2102
  const op = g("opacity");
1969
- if (op && parseFloat(op) < 1) add("opacity", op);
2103
+ if (op && parseFloat(op) < 1) add("opacity", op, ["opacity"]);
2104
+ const ov = g("overflow");
2105
+ if (ov && ov !== "visible") add("overflow", ov, ["overflow", "overflow-x", "overflow-y"]);
2106
+ const z = g("z-index");
2107
+ if (z && z !== "auto") add("z-index", z, ["z-index"]);
2108
+ const tr = g("transform");
2109
+ if (tr && tr !== "none") add("transform", clip(tr, 22), ["transform"]);
1970
2110
  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("");
2111
+ 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>`;
2112
+ const items = collapsed ? "" : legend + rows.map(
2113
+ (r) => `<div class="uiv-rrow"><span class="uiv-rk">${r.k}</span><span class="uiv-rv${r.cls}">${r.v}</span></div>`
2114
+ ).join("");
1972
2115
  return `<div class="uiv-sec">${this.accordionTitle("Current styles")}<div class="uiv-readout">${items}</div></div>`;
1973
2116
  }
1974
2117
  /** Breakpoint scope switcher: shows the PROJECT's breakpoints + the live window one. */
@@ -2117,6 +2260,13 @@ var Uivisor = class {
2117
2260
  const edited = this.st()?.record.changes.some(
2118
2261
  (c) => c.property === css && c.after.designToken
2119
2262
  );
2263
+ if (cat === "color") {
2264
+ const swatches = list.map((t) => {
2265
+ const on = near?.exact && near.token.cssVar === t.cssVar;
2266
+ 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>`;
2267
+ }).join("");
2268
+ return `<div class="uiv-ctl"><span class="clabel uiv-tlabel">${label}</span><div class="cfield uiv-swatches">${swatches}</div><span></span></div>`;
2269
+ }
2120
2270
  const head = `<option value="">${near && !near.exact ? `\u2248 ${escapeHtml(near.token.name)} \xB7 pick token` : "\u2014 pick token \u2014"}</option>`;
2121
2271
  const opts = list.map(
2122
2272
  (t) => `<option value="${escapeAttr(t.cssVar)}"${near?.exact && near.token.cssVar === t.cssVar ? " selected" : ""}>${escapeHtml(`${t.name} \xB7 ${t.value}`)}</option>`
@@ -2218,6 +2368,15 @@ var Uivisor = class {
2218
2368
  if (token) this.applyToken(css, token);
2219
2369
  });
2220
2370
  });
2371
+ root.querySelectorAll(".uiv-swatch").forEach((node) => {
2372
+ const btn = node;
2373
+ const css = btn.getAttribute("data-css");
2374
+ const cssVar = btn.getAttribute("data-var");
2375
+ btn.addEventListener("click", () => {
2376
+ const token = this.designSystem().tokens.find((t) => t.cssVar === cssVar);
2377
+ if (token) this.applyToken(css, token);
2378
+ });
2379
+ });
2221
2380
  root.querySelectorAll(".uiv-expand").forEach((node) => {
2222
2381
  const btn = node;
2223
2382
  const key = btn.getAttribute("data-key");
@@ -2364,7 +2523,8 @@ var Uivisor = class {
2364
2523
  const loc = id.source.confidence !== "none" ? `${id.source.file}:${id.source.line}` : id.componentName || "";
2365
2524
  const chgs = collapseChanges(r.changes).map((c) => {
2366
2525
  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>` : "";
2526
+ const tokLabel = c.after.token || (c.after.designToken ? `var(${c.after.designToken})` : "");
2527
+ const tok = tokLabel ? ` <span class="tok">${escapeHtml(tokLabel)}</span>` : "";
2368
2528
  return `<div class="uiv-jchg">${bp}${c.property}: ${c.before.computed} \u2192 ${c.after.computed}${tok}</div>`;
2369
2529
  }).join("");
2370
2530
  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>`;
@@ -2449,7 +2609,7 @@ var Uivisor = class {
2449
2609
  this.states.set(ent.el, st);
2450
2610
  const targets = st.record.target === "all" ? this.siblingsOf(ent.el) : [ent.el];
2451
2611
  for (const c of st.record.changes) {
2452
- for (const e of targets) applyOverride(e, c.property, c.after.computed);
2612
+ for (const e of targets) applyOverride(e, c.property, c.live ?? c.after.computed);
2453
2613
  st.applied.add(c.property);
2454
2614
  }
2455
2615
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uivisor",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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",