uivisor 0.1.6 → 0.1.8
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.
- package/dist/overlay/index.js +219 -61
- package/package.json +1 -1
package/dist/overlay/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/overlay/breakpoint.ts
|
|
2
2
|
var TAILWIND = {
|
|
3
3
|
name: "tailwind",
|
|
4
|
+
dir: "min",
|
|
4
5
|
breakpoints: [
|
|
5
6
|
{ name: "sm", minWidth: 640 },
|
|
6
7
|
{ name: "md", minWidth: 768 },
|
|
@@ -9,13 +10,44 @@ var TAILWIND = {
|
|
|
9
10
|
{ name: "2xl", minWidth: 1536 }
|
|
10
11
|
]
|
|
11
12
|
};
|
|
13
|
+
function appliesAt(dir, threshold, width) {
|
|
14
|
+
return dir === "min" ? width >= threshold : width <= threshold;
|
|
15
|
+
}
|
|
16
|
+
function priority(dir, threshold) {
|
|
17
|
+
return dir === "min" ? threshold : -threshold;
|
|
18
|
+
}
|
|
19
|
+
function baseThreshold(dir) {
|
|
20
|
+
return dir === "min" ? 0 : Infinity;
|
|
21
|
+
}
|
|
12
22
|
function activeBreakpoint(width, system = TAILWIND) {
|
|
13
|
-
let active = { name: "base", minWidth:
|
|
23
|
+
let active = { name: "base", minWidth: baseThreshold(system.dir) };
|
|
24
|
+
let best = priority(system.dir, active.minWidth);
|
|
14
25
|
for (const bp of system.breakpoints) {
|
|
15
|
-
if (
|
|
26
|
+
if (!appliesAt(system.dir, bp.minWidth, width)) continue;
|
|
27
|
+
const p = priority(system.dir, bp.minWidth);
|
|
28
|
+
if (p > best) {
|
|
29
|
+
best = p;
|
|
30
|
+
active = { name: bp.name, minWidth: bp.minWidth };
|
|
31
|
+
}
|
|
16
32
|
}
|
|
17
33
|
return active;
|
|
18
34
|
}
|
|
35
|
+
function effectiveBreakpoint(editedNames, width, system = TAILWIND) {
|
|
36
|
+
const stops = { base: baseThreshold(system.dir) };
|
|
37
|
+
for (const bp of system.breakpoints) stops[bp.name] = bp.minWidth;
|
|
38
|
+
let winner = null;
|
|
39
|
+
let best = -Infinity;
|
|
40
|
+
for (const name of editedNames) {
|
|
41
|
+
const threshold = stops[name];
|
|
42
|
+
if (threshold == null || !appliesAt(system.dir, threshold, width)) continue;
|
|
43
|
+
const p = priority(system.dir, threshold);
|
|
44
|
+
if (winner === null || p > best) {
|
|
45
|
+
best = p;
|
|
46
|
+
winner = name;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return winner;
|
|
50
|
+
}
|
|
19
51
|
function currentBreakpoint(system = TAILWIND) {
|
|
20
52
|
const width = typeof window !== "undefined" ? window.innerWidth : 0;
|
|
21
53
|
return activeBreakpoint(width, system);
|
|
@@ -32,18 +64,23 @@ function nameForWidth(px2) {
|
|
|
32
64
|
}
|
|
33
65
|
function detectBreakpoints() {
|
|
34
66
|
if (typeof document === "undefined") return TAILWIND;
|
|
35
|
-
const
|
|
67
|
+
const mins = /* @__PURE__ */ new Set();
|
|
68
|
+
const maxes = /* @__PURE__ */ new Set();
|
|
69
|
+
const grab = (text, re, into) => {
|
|
70
|
+
const m = re.exec(text);
|
|
71
|
+
if (m) {
|
|
72
|
+
const val = parseFloat(m[1]);
|
|
73
|
+
const px2 = m[2].toLowerCase() === "px" ? val : val * 16;
|
|
74
|
+
if (px2 >= 240 && px2 <= 4096) into.add(Math.round(px2));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
36
77
|
const visit = (rules) => {
|
|
37
78
|
if (!rules) return;
|
|
38
79
|
for (let i = 0; i < rules.length; i++) {
|
|
39
80
|
const rule = rules[i];
|
|
40
81
|
if (rule.type === 4 && rule.media) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const val = parseFloat(m[1]);
|
|
44
|
-
const px2 = m[2].toLowerCase() === "px" ? val : val * 16;
|
|
45
|
-
if (px2 >= 240 && px2 <= 4096) widths.add(Math.round(px2));
|
|
46
|
-
}
|
|
82
|
+
grab(rule.media.mediaText, /min-width:\s*(\d*\.?\d+)(px|rem|em)/i, mins);
|
|
83
|
+
grab(rule.media.mediaText, /max-width:\s*(\d*\.?\d+)(px|rem|em)/i, maxes);
|
|
47
84
|
visit(rule.cssRules);
|
|
48
85
|
} else if (rule.cssRules) {
|
|
49
86
|
visit(rule.cssRules);
|
|
@@ -56,10 +93,13 @@ function detectBreakpoints() {
|
|
|
56
93
|
} catch {
|
|
57
94
|
}
|
|
58
95
|
}
|
|
96
|
+
const useMax = maxes.size > mins.size;
|
|
97
|
+
const widths = useMax ? maxes : mins;
|
|
59
98
|
const sorted = [...widths].sort((a, b) => a - b);
|
|
60
99
|
if (!sorted.length) return TAILWIND;
|
|
61
100
|
return {
|
|
62
101
|
name: "detected",
|
|
102
|
+
dir: useMax ? "max" : "min",
|
|
63
103
|
breakpoints: sorted.map((w) => ({ name: nameForWidth(w), minWidth: w }))
|
|
64
104
|
};
|
|
65
105
|
}
|
|
@@ -366,7 +406,11 @@ var ICONS = {
|
|
|
366
406
|
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
407
|
width: sv('<path d="M1.5 8 H14.5 M4 5 L1.5 8 L4 11 M12 5 L14.5 8 L12 11"/>'),
|
|
368
408
|
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"/>')
|
|
409
|
+
chevron: sv('<path d="M6 4 L10 8 L6 12"/>'),
|
|
410
|
+
phone: sv('<rect x="5" y="1.8" width="6" height="12.4" rx="1.6"/><path d="M7 12.4h2"/>'),
|
|
411
|
+
tablet: sv('<rect x="3.3" y="2.2" width="9.4" height="11.6" rx="1.6"/><path d="M7 11.7h2"/>'),
|
|
412
|
+
desktop: sv('<rect x="1.8" y="2.8" width="12.4" height="8" rx="1"/><path d="M5.8 14h4.4 M8 10.8v3.2"/>'),
|
|
413
|
+
live: sv('<circle cx="8" cy="8" r="2"/><path d="M4.6 4.6a4.8 4.8 0 0 0 0 6.8 M11.4 4.6a4.8 4.8 0 0 1 0 6.8"/>')
|
|
370
414
|
};
|
|
371
415
|
var SECTIONS = [
|
|
372
416
|
{
|
|
@@ -1082,12 +1126,14 @@ var CSS = (
|
|
|
1082
1126
|
}
|
|
1083
1127
|
.uiv-framewrap.show { display: flex; }
|
|
1084
1128
|
.uiv-framebar {
|
|
1085
|
-
display: flex; align-items: center;
|
|
1086
|
-
height:
|
|
1129
|
+
display: flex; align-items: center; gap: 12px;
|
|
1130
|
+
height: 46px; padding: 0 14px; color: #e4e4e7; font-size: 12px; flex: 0 0 auto;
|
|
1087
1131
|
border-bottom: 1px solid #27272a;
|
|
1088
1132
|
}
|
|
1089
|
-
.uiv-
|
|
1090
|
-
.uiv-
|
|
1133
|
+
.uiv-framechips { display: flex; gap: 6px; flex: 1; justify-content: center; flex-wrap: wrap; }
|
|
1134
|
+
.uiv-framew { font-family: ui-monospace, monospace; color: #c7d2fe; font-weight: 600;
|
|
1135
|
+
white-space: nowrap; flex: 0 0 auto; }
|
|
1136
|
+
.uiv-framex { cursor: pointer; color: #a1a1aa; font-size: 14px; flex: 0 0 auto; }
|
|
1091
1137
|
.uiv-framex:hover { color: #fff; }
|
|
1092
1138
|
.uiv-framestage {
|
|
1093
1139
|
flex: 1; display: flex; align-items: stretch; justify-content: center;
|
|
@@ -1151,7 +1197,10 @@ var CSS = (
|
|
|
1151
1197
|
border-radius: 6px; padding: 3px 8px; font-size: 11px; font-weight: 600;
|
|
1152
1198
|
font-family: ui-monospace, monospace;
|
|
1153
1199
|
}
|
|
1200
|
+
.uiv-chip { display: inline-flex; align-items: center; gap: 4px; }
|
|
1201
|
+
.uiv-chip svg { width: 13px; height: 13px; opacity: .85; }
|
|
1154
1202
|
.uiv-chip:hover, .uiv-clschip:hover { color: #fff; background: #3f3f46; }
|
|
1203
|
+
.uiv-chip.on svg { opacity: 1; }
|
|
1155
1204
|
.uiv-chip.win { border-color: #52525b; color: #d4d4d8; }
|
|
1156
1205
|
.uiv-chip.on, .uiv-clschip.on { background: #4f46e5; border-color: #6366f1; color: #fff; }
|
|
1157
1206
|
.uiv-bphint { margin-top: 7px; font-size: 10px; color: #71717a; line-height: 1.4; }
|
|
@@ -1191,12 +1240,20 @@ var CSS = (
|
|
|
1191
1240
|
.uiv-rk { color: #71717a; }
|
|
1192
1241
|
.uiv-rv { color: #fff; word-break: break-all; display: flex; align-items: center; gap: 6px; }
|
|
1193
1242
|
.uiv-rv.changed { color: #4ade80; } /* edited in uivisor \u2192 green */
|
|
1194
|
-
|
|
1195
|
-
|
|
1243
|
+
|
|
1244
|
+
/* control-row state: file (authored) \xB7 edited (this breakpoint) \xB7 auto (computed) */
|
|
1245
|
+
.uiv-ctl.st-file > .clabel { color: #e4e4e7; }
|
|
1246
|
+
.uiv-ctl.st-edited > .clabel { color: #4ade80; }
|
|
1247
|
+
.uiv-ctl.st-auto > .clabel { color: #6b7280; }
|
|
1248
|
+
.uiv-ctl.st-inherit > .clabel { color: #38bdf8; } /* value cascaded from another bp */
|
|
1249
|
+
.uiv-inh { font-size: 9px; font-weight: 700; color: #38bdf8; font-family: ui-monospace, monospace;
|
|
1250
|
+
background: #0c4a6e55; border: 1px solid #0369a1; border-radius: 4px; padding: 0 3px; margin-left: 2px; }
|
|
1251
|
+
.uiv-leg { display: flex; gap: 12px; padding: 8px 12px 2px; font-size: 9px;
|
|
1196
1252
|
text-transform: uppercase; letter-spacing: .4px; }
|
|
1197
1253
|
.uiv-lg { color: #e4e4e7; display: flex; align-items: center; gap: 4px; } /* file = white */
|
|
1198
1254
|
.uiv-lg::before { content: ''; width: 7px; height: 7px; border-radius: 2px; background: currentColor; }
|
|
1199
1255
|
.uiv-lg.edit { color: #4ade80; }
|
|
1256
|
+
.uiv-lg.inh { color: #38bdf8; }
|
|
1200
1257
|
.uiv-lg.auto { color: #6b7280; }
|
|
1201
1258
|
.uiv-sw { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
|
|
1202
1259
|
border: 1px solid rgba(255,255,255,0.2); flex: 0 0 auto; }
|
|
@@ -1299,6 +1356,15 @@ var CSS = (
|
|
|
1299
1356
|
// src/overlay/index.ts
|
|
1300
1357
|
var counter = 0;
|
|
1301
1358
|
var round2 = (n) => Math.round(n * 100) / 100;
|
|
1359
|
+
var INHERITED_PROPS = /* @__PURE__ */ new Set([
|
|
1360
|
+
"font-size",
|
|
1361
|
+
"font-weight",
|
|
1362
|
+
"line-height",
|
|
1363
|
+
"letter-spacing",
|
|
1364
|
+
"color",
|
|
1365
|
+
"text-align",
|
|
1366
|
+
"font-family"
|
|
1367
|
+
]);
|
|
1302
1368
|
var Uivisor = class {
|
|
1303
1369
|
constructor() {
|
|
1304
1370
|
this.enabled = false;
|
|
@@ -1379,7 +1445,10 @@ var Uivisor = class {
|
|
|
1379
1445
|
const winBp = currentBreakpoint(this.bpSystem()).name;
|
|
1380
1446
|
if (winBp !== this.lastWinBp) {
|
|
1381
1447
|
this.lastWinBp = winBp;
|
|
1382
|
-
if (this.enabled && this.selected)
|
|
1448
|
+
if (this.enabled && this.selected) {
|
|
1449
|
+
this.reapplyScope();
|
|
1450
|
+
this.renderBody();
|
|
1451
|
+
}
|
|
1383
1452
|
}
|
|
1384
1453
|
};
|
|
1385
1454
|
this.reposition = () => {
|
|
@@ -1442,7 +1511,7 @@ var Uivisor = class {
|
|
|
1442
1511
|
this.root.innerHTML = `
|
|
1443
1512
|
<style>${CSS}</style>
|
|
1444
1513
|
<div class="uiv-framewrap">
|
|
1445
|
-
<div class="uiv-framebar"><span class="uiv-framew">768px</span><span class="uiv-framex" title="Exit responsive">\u2715
|
|
1514
|
+
<div class="uiv-framebar"><div class="uiv-framechips uiv-chips"></div><span class="uiv-framew">768px</span><span class="uiv-framex" title="Exit responsive">\u2715</span></div>
|
|
1446
1515
|
<div class="uiv-framestage">
|
|
1447
1516
|
<div class="uiv-framehost">
|
|
1448
1517
|
<iframe class="uiv-frame" data-uiv-frame="1"></iframe>
|
|
@@ -1645,6 +1714,7 @@ var Uivisor = class {
|
|
|
1645
1714
|
const up = () => {
|
|
1646
1715
|
handle.removeEventListener("pointermove", move);
|
|
1647
1716
|
handle.removeEventListener("pointerup", up);
|
|
1717
|
+
this.reapplyScope();
|
|
1648
1718
|
this.renderBody();
|
|
1649
1719
|
};
|
|
1650
1720
|
handle.addEventListener("pointermove", move);
|
|
@@ -1680,6 +1750,7 @@ var Uivisor = class {
|
|
|
1680
1750
|
dimUnit: {}
|
|
1681
1751
|
});
|
|
1682
1752
|
}
|
|
1753
|
+
if (el) this.reapplyScope();
|
|
1683
1754
|
this.reposition();
|
|
1684
1755
|
this.renderBody();
|
|
1685
1756
|
}
|
|
@@ -1706,6 +1777,11 @@ var Uivisor = class {
|
|
|
1706
1777
|
const el = this.selected;
|
|
1707
1778
|
const st = this.st();
|
|
1708
1779
|
if (!el || !st) return "";
|
|
1780
|
+
const ch = this.effectiveChange(css);
|
|
1781
|
+
if (ch) {
|
|
1782
|
+
const v = ch.live ?? ch.after.computed;
|
|
1783
|
+
return v.includes("var(") ? this.computedVal(css) || v : v;
|
|
1784
|
+
}
|
|
1709
1785
|
const inline = el.style.getPropertyValue(css);
|
|
1710
1786
|
if (inline && !inline.includes("var(")) return inline;
|
|
1711
1787
|
return this.computedVal(css) || st.original[css] || "";
|
|
@@ -1728,7 +1804,10 @@ var Uivisor = class {
|
|
|
1728
1804
|
isChanged(cssList) {
|
|
1729
1805
|
const st = this.st();
|
|
1730
1806
|
if (!st) return false;
|
|
1731
|
-
|
|
1807
|
+
const scope = this.activeScope();
|
|
1808
|
+
return cssList.some(
|
|
1809
|
+
(c) => st.record.changes.some((ch) => ch.property === c && ch.breakpoint === scope.name)
|
|
1810
|
+
);
|
|
1732
1811
|
}
|
|
1733
1812
|
selectCurrent(css) {
|
|
1734
1813
|
let v = this.liveVal(css).trim();
|
|
@@ -1773,16 +1852,36 @@ var Uivisor = class {
|
|
|
1773
1852
|
}
|
|
1774
1853
|
/** Re-apply recorded overrides after the target (all ↔ one) changes. */
|
|
1775
1854
|
reapplyForTarget() {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1855
|
+
this.reapplyScope();
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Project the live preview for the ACTIVE breakpoint: strip every override we
|
|
1859
|
+
* applied, then re-apply ONLY the changes recorded for the current scope. This
|
|
1860
|
+
* is what makes per-breakpoint edits behave — set padding 20 at xl, 10 at md,
|
|
1861
|
+
* and each breakpoint shows (and previews) its own value instead of one global
|
|
1862
|
+
* inline override leaking across all of them.
|
|
1863
|
+
*/
|
|
1864
|
+
reapplyScope() {
|
|
1865
|
+
const width = this.activeWidth();
|
|
1866
|
+
const sys = this.bpSystem();
|
|
1867
|
+
for (const [el, st] of this.states) {
|
|
1868
|
+
const sibs = this.siblingsOf(el);
|
|
1869
|
+
const targets = st.record.target === "all" ? sibs : [el];
|
|
1870
|
+
for (const css of st.applied) for (const e of sibs) removeOverride(e, css);
|
|
1871
|
+
st.applied = /* @__PURE__ */ new Set();
|
|
1872
|
+
const byProp = /* @__PURE__ */ new Map();
|
|
1873
|
+
for (const c of st.record.changes) {
|
|
1874
|
+
const arr = byProp.get(c.property) ?? [];
|
|
1875
|
+
arr.push(c);
|
|
1876
|
+
byProp.set(c.property, arr);
|
|
1877
|
+
}
|
|
1878
|
+
for (const [prop, changes] of byProp) {
|
|
1879
|
+
const eff = effectiveBreakpoint(changes.map((c2) => c2.breakpoint), width, sys);
|
|
1880
|
+
if (!eff) continue;
|
|
1881
|
+
const c = changes.find((x) => x.breakpoint === eff);
|
|
1882
|
+
for (const e of targets) applyOverride(e, prop, c.live ?? c.after.computed);
|
|
1883
|
+
st.applied.add(prop);
|
|
1884
|
+
}
|
|
1786
1885
|
}
|
|
1787
1886
|
this.reposition();
|
|
1788
1887
|
}
|
|
@@ -1794,6 +1893,34 @@ var Uivisor = class {
|
|
|
1794
1893
|
if (this.responsive) return activeBreakpoint(this.frameWidth, sys);
|
|
1795
1894
|
return currentBreakpoint(sys);
|
|
1796
1895
|
}
|
|
1896
|
+
/** The width the inspector is scoped to (virtual screen, else real window). */
|
|
1897
|
+
activeWidth() {
|
|
1898
|
+
if (this.responsive) return this.frameWidth;
|
|
1899
|
+
return typeof window !== "undefined" ? window.innerWidth : 0;
|
|
1900
|
+
}
|
|
1901
|
+
/** The recorded change that wins the breakpoint cascade for `css` at the active
|
|
1902
|
+
* width — i.e. the value effective here, set on this breakpoint or inherited. */
|
|
1903
|
+
effectiveChange(css) {
|
|
1904
|
+
const st = this.st();
|
|
1905
|
+
if (!st) return null;
|
|
1906
|
+
const changes = st.record.changes.filter((c) => c.property === css);
|
|
1907
|
+
if (!changes.length) return null;
|
|
1908
|
+
const eff = effectiveBreakpoint(
|
|
1909
|
+
changes.map((c) => c.breakpoint),
|
|
1910
|
+
this.activeWidth(),
|
|
1911
|
+
this.bpSystem()
|
|
1912
|
+
);
|
|
1913
|
+
return eff ? changes.find((c) => c.breakpoint === eff) ?? null : null;
|
|
1914
|
+
}
|
|
1915
|
+
/** If `css`'s effective value is INHERITED from another breakpoint, its name. */
|
|
1916
|
+
inheritedFrom(props) {
|
|
1917
|
+
const scope = this.activeScope();
|
|
1918
|
+
for (const p of props) {
|
|
1919
|
+
const e = this.effectiveChange(p);
|
|
1920
|
+
if (e && e.breakpoint !== scope.name) return e.breakpoint;
|
|
1921
|
+
}
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1797
1924
|
recordProps(cssList) {
|
|
1798
1925
|
const el = this.selected;
|
|
1799
1926
|
const st = this.st();
|
|
@@ -1897,6 +2024,7 @@ var Uivisor = class {
|
|
|
1897
2024
|
<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>
|
|
1898
2025
|
${this.journalHtml()}
|
|
1899
2026
|
`;
|
|
2027
|
+
if (this.responsive) this.renderFrameBar();
|
|
1900
2028
|
this.bindControls();
|
|
1901
2029
|
return;
|
|
1902
2030
|
}
|
|
@@ -1916,6 +2044,7 @@ var Uivisor = class {
|
|
|
1916
2044
|
${this.controlsHtml(this.context(this.selected))}
|
|
1917
2045
|
${this.journalHtml()}
|
|
1918
2046
|
`;
|
|
2047
|
+
if (this.responsive) this.renderFrameBar();
|
|
1919
2048
|
this.bindControls();
|
|
1920
2049
|
}
|
|
1921
2050
|
/** Small indicator: how many design tokens were detected (or a hint if none). */
|
|
@@ -1974,6 +2103,21 @@ var Uivisor = class {
|
|
|
1974
2103
|
cache.set(el, out);
|
|
1975
2104
|
return out;
|
|
1976
2105
|
}
|
|
2106
|
+
/** State class for an editable control's row: edited (green, at this breakpoint)
|
|
2107
|
+
* · inherit (a value cascaded from another breakpoint) · file (white, authored
|
|
2108
|
+
* in CSS) · auto (grey, browser-computed/default). */
|
|
2109
|
+
controlStateClass(props) {
|
|
2110
|
+
if (this.isChanged(props)) return " st-edited";
|
|
2111
|
+
if (this.inheritedFrom(props)) return " st-inherit";
|
|
2112
|
+
const inherit = props.some((p) => INHERITED_PROPS.has(p));
|
|
2113
|
+
return this.isAuthored(props, inherit) ? " st-file" : " st-auto";
|
|
2114
|
+
}
|
|
2115
|
+
/** A control's label, with an "inherited from {bp}" badge when the value cascaded. */
|
|
2116
|
+
ctlLabel(label, props) {
|
|
2117
|
+
const from = this.inheritedFrom(props);
|
|
2118
|
+
const badge = from ? ` <span class="uiv-inh" title="inherited from ${escapeAttr(from)} \u2014 not set at this breakpoint">\u2923${escapeHtml(from)}</span>` : "";
|
|
2119
|
+
return `<span class="clabel">${label}${badge}</span>`;
|
|
2120
|
+
}
|
|
1977
2121
|
/** Is any of `props` authored in the project CSS? For inherited properties we
|
|
1978
2122
|
* also walk ancestors (a body/parent font rule still counts as "from the file"). */
|
|
1979
2123
|
isAuthored(props, inherit) {
|
|
@@ -2014,14 +2158,9 @@ var Uivisor = class {
|
|
|
2014
2158
|
};
|
|
2015
2159
|
const px4 = (pre, suf = "") => [`${pre}-top${suf}`, `${pre}-right${suf}`, `${pre}-bottom${suf}`, `${pre}-left${suf}`];
|
|
2016
2160
|
const clip = (s, n = 30) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
2017
|
-
const changedSet = new Set(this.st()?.record.changes.map((c) => c.property) ?? []);
|
|
2018
|
-
const stateCls = (props, inherit = false) => {
|
|
2019
|
-
if (props.some((p) => changedSet.has(p))) return " changed";
|
|
2020
|
-
return this.isAuthored(props, inherit) ? "" : " auto";
|
|
2021
|
-
};
|
|
2022
2161
|
const rows = [];
|
|
2023
|
-
const add = (k, v,
|
|
2024
|
-
if (v !== "" && v != null) rows.push({ k, v
|
|
2162
|
+
const add = (k, v, _props = [], _inherit = false) => {
|
|
2163
|
+
if (v !== "" && v != null) rows.push({ k, v });
|
|
2025
2164
|
};
|
|
2026
2165
|
const disp = g("display");
|
|
2027
2166
|
add("display", disp, ["display"]);
|
|
@@ -2108,29 +2247,49 @@ var Uivisor = class {
|
|
|
2108
2247
|
const tr = g("transform");
|
|
2109
2248
|
if (tr && tr !== "none") add("transform", clip(tr, 22), ["transform"]);
|
|
2110
2249
|
const collapsed = this.collapsedSecs.has("Current styles");
|
|
2111
|
-
const
|
|
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("");
|
|
2250
|
+
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("");
|
|
2115
2251
|
return `<div class="uiv-sec">${this.accordionTitle("Current styles")}<div class="uiv-readout">${items}</div></div>`;
|
|
2116
2252
|
}
|
|
2117
|
-
/**
|
|
2118
|
-
|
|
2253
|
+
/** A device icon for a breakpoint chip, by its threshold (size proxy). */
|
|
2254
|
+
bpIcon(name) {
|
|
2255
|
+
if (name === "live") return ICONS.live;
|
|
2256
|
+
const px2 = name === "base" ? 0 : this.bpSystem().breakpoints.find((b) => b.name === name)?.minWidth ?? 0;
|
|
2257
|
+
if (px2 < 768) return ICONS.phone;
|
|
2258
|
+
if (px2 < 1024) return ICONS.tablet;
|
|
2259
|
+
return ICONS.desktop;
|
|
2260
|
+
}
|
|
2261
|
+
/** Breakpoint chips (Live + each project breakpoint) with device icons. Reused by
|
|
2262
|
+
* the panel (Live mode) and the bar over the virtual screen (responsive mode). */
|
|
2263
|
+
breakpointChipsHtml() {
|
|
2119
2264
|
const sys = this.bpSystem();
|
|
2120
|
-
const
|
|
2121
|
-
const names = ["base", ...bps.map((b) => b.name)];
|
|
2265
|
+
const names = ["base", ...sys.breakpoints.map((b) => b.name)];
|
|
2122
2266
|
const frameBp = this.responsive ? activeBreakpoint(this.frameWidth, sys).name : null;
|
|
2123
2267
|
const winBp = currentBreakpoint(sys).name;
|
|
2124
|
-
const
|
|
2125
|
-
const
|
|
2268
|
+
const chip = (n, on, title) => `<button class="uiv-chip${on ? " on" : ""}" data-bp="${n}" title="${escapeAttr(title)}">${this.bpIcon(n)}<span>${n === "live" ? "Live" : n}</span></button>`;
|
|
2269
|
+
const live = chip("live", !this.responsive, "Follow your real browser window");
|
|
2126
2270
|
const chips = names.map((n) => {
|
|
2127
2271
|
const active = this.responsive ? n === frameBp : n === winBp;
|
|
2128
|
-
const px2 = n === "base" ? 0 :
|
|
2129
|
-
return
|
|
2272
|
+
const px2 = n === "base" ? 0 : sys.breakpoints.find((b) => b.name === n).minWidth;
|
|
2273
|
+
return chip(n, active, sys.dir === "min" ? `\u2265 ${px2}px` : `\u2264 ${px2}px`);
|
|
2130
2274
|
}).join("");
|
|
2275
|
+
return live + chips;
|
|
2276
|
+
}
|
|
2277
|
+
/** Panel breakpoint bar — shown only in Live mode (in responsive mode the bar
|
|
2278
|
+
* lives over the virtual screen instead). */
|
|
2279
|
+
breakpointBarHtml() {
|
|
2280
|
+
if (this.responsive) return "";
|
|
2281
|
+
const sys = this.bpSystem();
|
|
2282
|
+
const winBp = currentBreakpoint(sys).name;
|
|
2283
|
+
const liveW = typeof window !== "undefined" ? window.innerWidth : 0;
|
|
2131
2284
|
const detected = sys.name === "detected" ? "" : " (defaults)";
|
|
2132
|
-
const
|
|
2133
|
-
|
|
2285
|
+
const cascade = sys.dir === "min" ? `Mobile-first: an edit applies to this breakpoint and <b>wider</b>.` : `Desktop-first: an edit applies to this breakpoint and <b>narrower</b>.`;
|
|
2286
|
+
const hint = `Live \u2014 window <b>${liveW}px</b> = <b>${winBp}</b>. ${cascade} Click a size to shrink the screen.`;
|
|
2287
|
+
return `<div class="uiv-sec"><div class="uiv-sectitle">Screen / breakpoint${detected}</div><div class="uiv-chips">${this.breakpointChipsHtml()}</div><div class="uiv-bphint">${hint}</div></div>`;
|
|
2288
|
+
}
|
|
2289
|
+
/** Populate the bar over the virtual screen (responsive mode) with the chips. */
|
|
2290
|
+
renderFrameBar() {
|
|
2291
|
+
const host = this.root.querySelector(".uiv-framechips");
|
|
2292
|
+
if (host) host.innerHTML = this.breakpointChipsHtml();
|
|
2134
2293
|
}
|
|
2135
2294
|
/** "Apply changes to": this element, an existing shared class, or a NEW class. */
|
|
2136
2295
|
targetHtml(st) {
|
|
@@ -2165,12 +2324,14 @@ var Uivisor = class {
|
|
|
2165
2324
|
return true;
|
|
2166
2325
|
}
|
|
2167
2326
|
controlsHtml(ctx) {
|
|
2168
|
-
|
|
2327
|
+
const legend = `<div class="uiv-leg"><span class="uiv-lg">file</span><span class="uiv-lg edit">edited</span><span class="uiv-lg inh">inherited</span><span class="uiv-lg auto">auto</span></div>`;
|
|
2328
|
+
const secs = SECTIONS.map((sec) => {
|
|
2169
2329
|
const controls = sec.controls.filter((c) => this.relevant(c, ctx));
|
|
2170
2330
|
if (!controls.length) return "";
|
|
2171
2331
|
const rows = this.collapsedSecs.has(sec.title) ? "" : controls.map((c) => this.controlRow(c)).join("");
|
|
2172
2332
|
return `<div class="uiv-sec">${this.accordionTitle(sec.title)}${rows}</div>`;
|
|
2173
2333
|
}).join("");
|
|
2334
|
+
return legend + secs;
|
|
2174
2335
|
}
|
|
2175
2336
|
/** A collapsible section header. Clicking it hides/shows the section's controls. */
|
|
2176
2337
|
accordionTitle(title) {
|
|
@@ -2245,7 +2406,7 @@ var Uivisor = class {
|
|
|
2245
2406
|
const d = this.dimDisplay(c);
|
|
2246
2407
|
const changed = this.isChanged([c.css]);
|
|
2247
2408
|
const units = c.units.map((u) => `<option value="${u}"${u === d.unit ? " selected" : ""}>${UNIT_LABELS[u] ?? u}</option>`).join("");
|
|
2248
|
-
return `<div class="uiv-ctl"
|
|
2409
|
+
return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<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>`;
|
|
2249
2410
|
}
|
|
2250
2411
|
/** A design-token picker row for a property, shown only when the project exposes
|
|
2251
2412
|
* tokens for that category. Picking a token applies its value + tags the prompt. */
|
|
@@ -2279,7 +2440,7 @@ var Uivisor = class {
|
|
|
2279
2440
|
const info = this.numInfo(cssList);
|
|
2280
2441
|
const changed = this.isChanged(cssList);
|
|
2281
2442
|
const open = this.expanded.has(c.key);
|
|
2282
|
-
let html = `<div class="uiv-ctl"
|
|
2443
|
+
let html = `<div class="uiv-ctl${this.controlStateClass(cssList)}">` + this.ctlLabel(c.label, cssList) + `<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>`;
|
|
2283
2444
|
if (open) {
|
|
2284
2445
|
html += `<div class="uiv-sides">` + c.sides.map((s) => {
|
|
2285
2446
|
const v = this.liveNum(s.css);
|
|
@@ -2297,7 +2458,7 @@ var Uivisor = class {
|
|
|
2297
2458
|
}
|
|
2298
2459
|
if (c.kind === "len") {
|
|
2299
2460
|
const v = this.liveNum(c.css);
|
|
2300
|
-
return `<div class="uiv-ctl"
|
|
2461
|
+
return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<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");
|
|
2301
2462
|
}
|
|
2302
2463
|
if (c.kind === "dim") {
|
|
2303
2464
|
return this.dimField(c);
|
|
@@ -2306,10 +2467,10 @@ var Uivisor = class {
|
|
|
2306
2467
|
const cur = this.selectCurrent(c.css);
|
|
2307
2468
|
const optList = cur && !c.options.includes(cur) ? [cur, ...c.options] : c.options;
|
|
2308
2469
|
const opts = optList.map((o) => `<option value="${o}"${o === cur ? " selected" : ""}>${o}</option>`).join("");
|
|
2309
|
-
return `<div class="uiv-ctl"
|
|
2470
|
+
return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<div class="cfield"><select class="uiv-sel${this.isChanged([c.css]) ? " changed" : ""}" data-css="${c.css}">${opts}</select></div><span></span></div>`;
|
|
2310
2471
|
}
|
|
2311
2472
|
const val = toHexInput(this.liveVal(c.css));
|
|
2312
|
-
return `<div class="uiv-ctl"
|
|
2473
|
+
return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<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");
|
|
2313
2474
|
}
|
|
2314
2475
|
bindControls() {
|
|
2315
2476
|
const root = this.root;
|
|
@@ -2410,6 +2571,7 @@ var Uivisor = class {
|
|
|
2410
2571
|
this.toggleResponsive(true);
|
|
2411
2572
|
} else {
|
|
2412
2573
|
this.setFrameWidth(w);
|
|
2574
|
+
this.reapplyScope();
|
|
2413
2575
|
this.renderBody();
|
|
2414
2576
|
}
|
|
2415
2577
|
});
|
|
@@ -2607,13 +2769,9 @@ var Uivisor = class {
|
|
|
2607
2769
|
for (const ent of snap.entries) {
|
|
2608
2770
|
const st = { record: clone(ent.record), original: { ...ent.original }, applied: /* @__PURE__ */ new Set(), dimUnit: { ...ent.dimUnit } };
|
|
2609
2771
|
this.states.set(ent.el, st);
|
|
2610
|
-
const targets = st.record.target === "all" ? this.siblingsOf(ent.el) : [ent.el];
|
|
2611
|
-
for (const c of st.record.changes) {
|
|
2612
|
-
for (const e of targets) applyOverride(e, c.property, c.live ?? c.after.computed);
|
|
2613
|
-
st.applied.add(c.property);
|
|
2614
|
-
}
|
|
2615
2772
|
}
|
|
2616
2773
|
this.selected = snap.selected && this.states.has(snap.selected) ? snap.selected : null;
|
|
2774
|
+
this.reapplyScope();
|
|
2617
2775
|
this.reposition();
|
|
2618
2776
|
this.renderBody();
|
|
2619
2777
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uivisor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|