uivisor 0.1.8 → 0.1.10
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 +198 -61
- package/package.json +1 -1
package/dist/overlay/index.js
CHANGED
|
@@ -410,7 +410,8 @@ var ICONS = {
|
|
|
410
410
|
phone: sv('<rect x="5" y="1.8" width="6" height="12.4" rx="1.6"/><path d="M7 12.4h2"/>'),
|
|
411
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
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"/>')
|
|
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"/>'),
|
|
414
|
+
all: sv('<path d="M8 2.5v11 M3.2 5.2l9.6 5.6 M12.8 5.2l-9.6 5.6"/>')
|
|
414
415
|
};
|
|
415
416
|
var SECTIONS = [
|
|
416
417
|
{
|
|
@@ -455,10 +456,10 @@ var SECTIONS = [
|
|
|
455
456
|
{
|
|
456
457
|
title: "Size",
|
|
457
458
|
controls: [
|
|
458
|
-
{ kind: "len", css: "width", label: "Width", icon: ICONS.width },
|
|
459
|
-
{ kind: "len", css: "height", label: "Height", icon: ICONS.height },
|
|
460
|
-
{ kind: "len", css: "max-width", label: "Max W", icon: ICONS.width },
|
|
461
|
-
{ kind: "len", css: "min-height", label: "Min H", icon: ICONS.height }
|
|
459
|
+
{ kind: "len", css: "width", label: "Width", icon: ICONS.width, hideWhenAuto: true },
|
|
460
|
+
{ kind: "len", css: "height", label: "Height", icon: ICONS.height, hideWhenAuto: true },
|
|
461
|
+
{ kind: "len", css: "max-width", label: "Max W", icon: ICONS.width, hideWhenAuto: true },
|
|
462
|
+
{ kind: "len", css: "min-height", label: "Min H", icon: ICONS.height, hideWhenAuto: true }
|
|
462
463
|
]
|
|
463
464
|
},
|
|
464
465
|
{
|
|
@@ -855,9 +856,29 @@ function renderPrompt(records) {
|
|
|
855
856
|
);
|
|
856
857
|
lines.push("");
|
|
857
858
|
lines.push(
|
|
858
|
-
"These are visual tweaks I made in the running app. Apply them to the source.
|
|
859
|
+
"These are visual tweaks I made in the running app. Apply them to the source \u2014 but do NOT just mechanically apply them. First analyze the markup: if a change exposes dead, redundant, or contradictory code, stop and tell me (or propose the cleanup) instead of blindly applying."
|
|
859
860
|
);
|
|
860
861
|
lines.push("");
|
|
862
|
+
lines.push("## Analyze the markup first (don\u2019t just change values)");
|
|
863
|
+
lines.push(
|
|
864
|
+
"- If a change effectively HIDES or empties an element (display:none, visibility:hidden, opacity:0, or collapsing its size to 0) and it isn\u2019t shown conditionally somewhere else, question whether the element/component is needed at all. Prefer DELETING the element and its now-unused styles/classes over leaving a permanently hidden node."
|
|
865
|
+
);
|
|
866
|
+
lines.push(
|
|
867
|
+
"- Flag CSS that has no effect in its context and ask why it\u2019s there \u2014 don\u2019t silently keep it:"
|
|
868
|
+
);
|
|
869
|
+
lines.push(
|
|
870
|
+
" \u2022 flex-direction / justify-content / align-items / flex-wrap / gap on an element whose display is NOT flex/grid (e.g. block);"
|
|
871
|
+
);
|
|
872
|
+
lines.push(" \u2022 flex-grow / flex-shrink / flex-basis / align-self / order when the PARENT isn\u2019t flex/grid;");
|
|
873
|
+
lines.push(" \u2022 top / right / bottom / left / z-index when position is static;");
|
|
874
|
+
lines.push(
|
|
875
|
+
" \u2022 width / height on an inline element; a value identical to the inherited/default (a no-op)."
|
|
876
|
+
);
|
|
877
|
+
lines.push(
|
|
878
|
+
"- If a tweak only makes sense because of an underlying layout problem, fix the cause, don\u2019t patch the symptom. Remove dead code rather than piling on more utilities/overrides."
|
|
879
|
+
);
|
|
880
|
+
lines.push("- Per-element notes flagged below (\u26A0) are concrete instances I already detected \u2014 address them.");
|
|
881
|
+
lines.push("");
|
|
861
882
|
let anyClassTarget = false;
|
|
862
883
|
let anyNewClass = false;
|
|
863
884
|
let anyDesignToken = false;
|
|
@@ -926,6 +947,10 @@ function renderPrompt(records) {
|
|
|
926
947
|
);
|
|
927
948
|
}
|
|
928
949
|
}
|
|
950
|
+
if (r.smells?.length) {
|
|
951
|
+
lines.push(`- \u26A0 Analyze this element (don\u2019t just apply \u2014 these look off):`);
|
|
952
|
+
for (const s of r.smells) lines.push(` - ${s}`);
|
|
953
|
+
}
|
|
929
954
|
lines.push("");
|
|
930
955
|
});
|
|
931
956
|
lines.push("### Rules");
|
|
@@ -1242,10 +1267,22 @@ var CSS = (
|
|
|
1242
1267
|
.uiv-rv.changed { color: #4ade80; } /* edited in uivisor \u2192 green */
|
|
1243
1268
|
|
|
1244
1269
|
/* control-row state: file (authored) \xB7 edited (this breakpoint) \xB7 auto (computed) */
|
|
1245
|
-
|
|
1246
|
-
.uiv-ctl.st-
|
|
1247
|
-
.uiv-ctl.st-
|
|
1248
|
-
.uiv-ctl.st-
|
|
1270
|
+
/* 3-state colour on BOTH the label and the value (input / select text) */
|
|
1271
|
+
.uiv-ctl.st-file > .clabel,
|
|
1272
|
+
.uiv-ctl.st-file .uiv-num input, .uiv-ctl.st-file select.uiv-sel { color: #e4e4e7; }
|
|
1273
|
+
.uiv-ctl.st-edited > .clabel,
|
|
1274
|
+
.uiv-ctl.st-edited .uiv-num input, .uiv-ctl.st-edited select.uiv-sel { color: #4ade80; }
|
|
1275
|
+
.uiv-ctl.st-inherit > .clabel,
|
|
1276
|
+
.uiv-ctl.st-inherit .uiv-num input, .uiv-ctl.st-inherit select.uiv-sel { color: #38bdf8; } /* cascaded */
|
|
1277
|
+
.uiv-ctl.st-auto > .clabel,
|
|
1278
|
+
.uiv-ctl.st-auto .uiv-num input, .uiv-ctl.st-auto select.uiv-sel { color: #6b7280; }
|
|
1279
|
+
|
|
1280
|
+
/* "+" chips for hidden auto controls (width/height when not set) */
|
|
1281
|
+
.uiv-adds { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 2px; }
|
|
1282
|
+
.uiv-addctl { cursor: pointer; border: 1px dashed #52525b; background: transparent;
|
|
1283
|
+
color: #71717a; border-radius: 6px; padding: 3px 8px; font-size: 11px; font-weight: 600;
|
|
1284
|
+
font-family: ui-monospace, monospace; }
|
|
1285
|
+
.uiv-addctl:hover { color: #fff; border-color: #6366f1; }
|
|
1249
1286
|
.uiv-inh { font-size: 9px; font-weight: 700; color: #38bdf8; font-family: ui-monospace, monospace;
|
|
1250
1287
|
background: #0c4a6e55; border: 1px solid #0369a1; border-radius: 4px; padding: 0 3px; margin-left: 2px; }
|
|
1251
1288
|
.uiv-leg { display: flex; gap: 12px; padding: 8px 12px 2px; font-size: 9px;
|
|
@@ -1345,6 +1382,16 @@ var CSS = (
|
|
|
1345
1382
|
.uiv-btn.primary { background: #4f46e5; border-color: #6366f1; color: #fff; flex-basis: 100%; }
|
|
1346
1383
|
.uiv-btn.primary:hover { background: #4338ca; }
|
|
1347
1384
|
.uiv-btn.ghost { flex: 0 0 auto; }
|
|
1385
|
+
/* floating read-only "all styles" block, docked bottom-right, left of the panel */
|
|
1386
|
+
.uiv-info { position: fixed; right: 352px; bottom: 16px; z-index: 2147483646;
|
|
1387
|
+
width: 216px; max-height: 52vh; overflow: auto; display: none;
|
|
1388
|
+
background: rgba(24,24,27,0.86); color: #e4e4e7;
|
|
1389
|
+
border: 1px solid #3f3f46; border-radius: 10px; padding: 8px 10px;
|
|
1390
|
+
font-size: 11px; box-shadow: 0 8px 28px rgba(0,0,0,0.4); }
|
|
1391
|
+
.uiv-info.show { display: block; }
|
|
1392
|
+
.uiv-info-h { font-size: 10px; text-transform: uppercase; letter-spacing: .4px;
|
|
1393
|
+
color: #8b8b94; font-weight: 600; margin-bottom: 6px; }
|
|
1394
|
+
.uiv-info-sub { color: #52525b; }
|
|
1348
1395
|
.uiv-toast { position: fixed; right: 16px; bottom: 128px; z-index: 2147483647;
|
|
1349
1396
|
background: #22c55e; color: #052e16; padding: 8px 12px; border-radius: 8px;
|
|
1350
1397
|
font-size: 12px; font-weight: 600; display: none; }
|
|
@@ -1373,6 +1420,8 @@ var Uivisor = class {
|
|
|
1373
1420
|
this.expanded = /* @__PURE__ */ new Set();
|
|
1374
1421
|
/** Section titles collapsed in the accordion (per session). */
|
|
1375
1422
|
this.collapsedSecs = /* @__PURE__ */ new Set();
|
|
1423
|
+
/** Controls manually revealed via "+" (hideWhenAuto controls that are auto). */
|
|
1424
|
+
this.revealedCtls = /* @__PURE__ */ new Set();
|
|
1376
1425
|
/** Undo / redo stacks of full edit-state snapshots. */
|
|
1377
1426
|
this.undoStack = [];
|
|
1378
1427
|
this.redoStack = [];
|
|
@@ -1511,7 +1560,7 @@ var Uivisor = class {
|
|
|
1511
1560
|
this.root.innerHTML = `
|
|
1512
1561
|
<style>${CSS}</style>
|
|
1513
1562
|
<div class="uiv-framewrap">
|
|
1514
|
-
<div class="uiv-framebar"><div class="uiv-framechips uiv-chips"></div><span class="uiv-framew">768px</span><span class="uiv-framex" title="
|
|
1563
|
+
<div class="uiv-framebar"><div class="uiv-framechips uiv-chips"></div><span class="uiv-framew">768px</span><span class="uiv-framex" title="Turn uivisor off (Alt+U)">\u2715</span></div>
|
|
1515
1564
|
<div class="uiv-framestage">
|
|
1516
1565
|
<div class="uiv-framehost">
|
|
1517
1566
|
<iframe class="uiv-frame" data-uiv-frame="1"></iframe>
|
|
@@ -1523,6 +1572,7 @@ var Uivisor = class {
|
|
|
1523
1572
|
<div class="uiv-box sel"></div>
|
|
1524
1573
|
<div class="uiv-tag"></div>
|
|
1525
1574
|
<div class="uiv-fab" title="Toggle uivisor (Alt+U)">\u25CE</div>
|
|
1575
|
+
<div class="uiv-info"></div>
|
|
1526
1576
|
<div class="uiv-toast"></div>
|
|
1527
1577
|
<div class="uiv-panel">
|
|
1528
1578
|
<div class="uiv-head">
|
|
@@ -1550,7 +1600,7 @@ var Uivisor = class {
|
|
|
1550
1600
|
this.frame = this.q(".uiv-frame");
|
|
1551
1601
|
this.fab.addEventListener("click", () => this.toggle());
|
|
1552
1602
|
this.q(".uiv-x").addEventListener("click", () => this.toggle(false));
|
|
1553
|
-
this.q(".uiv-framex").addEventListener("click", () => this.
|
|
1603
|
+
this.q(".uiv-framex").addEventListener("click", () => this.toggle(false));
|
|
1554
1604
|
this.q(".copy-prompt").addEventListener("click", () => this.copyPrompt());
|
|
1555
1605
|
this.q(".copy-json").addEventListener("click", () => this.copyJSON());
|
|
1556
1606
|
this.q(".reset").addEventListener("click", () => this.resetSelected());
|
|
@@ -1596,11 +1646,26 @@ var Uivisor = class {
|
|
|
1596
1646
|
if (!this.enabled) {
|
|
1597
1647
|
this.hoverBox.style.display = "none";
|
|
1598
1648
|
this.tag.style.display = "none";
|
|
1649
|
+
this.renderInfo();
|
|
1599
1650
|
if (this.responsive) this.toggleResponsive(false);
|
|
1600
1651
|
} else {
|
|
1601
1652
|
this.scheduleBpRefresh();
|
|
1653
|
+
if (!this.responsive) {
|
|
1654
|
+
this.frameWidth = this.defaultFrameWidth();
|
|
1655
|
+
this.toggleResponsive(true);
|
|
1656
|
+
}
|
|
1602
1657
|
}
|
|
1603
1658
|
}
|
|
1659
|
+
/** Frame width on enable: the real window width (≈ the current breakpoint). */
|
|
1660
|
+
defaultFrameWidth() {
|
|
1661
|
+
return typeof window !== "undefined" ? window.innerWidth : 1280;
|
|
1662
|
+
}
|
|
1663
|
+
/** Frame width for the "all"/base chip: a phone-ish width in the base range. */
|
|
1664
|
+
baseFrameWidth() {
|
|
1665
|
+
const bps = this.bpSystem().breakpoints;
|
|
1666
|
+
const firstBp = bps.length ? bps[0].minWidth : 640;
|
|
1667
|
+
return this.bpSystem().dir === "min" ? Math.min(390, firstBp - 1) : 390;
|
|
1668
|
+
}
|
|
1604
1669
|
/** Stylesheets (esp. JIT/CDN Tailwind) load async — re-detect breakpoints a few
|
|
1605
1670
|
* times after enabling and re-render only if the set actually changed. */
|
|
1606
1671
|
scheduleBpRefresh() {
|
|
@@ -1696,7 +1761,7 @@ var Uivisor = class {
|
|
|
1696
1761
|
const host = this.q(".uiv-framehost");
|
|
1697
1762
|
host.style.width = `${this.frameWidth}px`;
|
|
1698
1763
|
const bp = activeBreakpoint(this.frameWidth, this.bpSystem()).name;
|
|
1699
|
-
this.q(".uiv-framew").textContent = `${this.frameWidth}px \xB7 ${bp}`;
|
|
1764
|
+
this.q(".uiv-framew").textContent = `${this.frameWidth}px \xB7 ${this.bpLabel(bp)}`;
|
|
1700
1765
|
this.updateBp();
|
|
1701
1766
|
this.reposition();
|
|
1702
1767
|
}
|
|
@@ -2025,6 +2090,7 @@ var Uivisor = class {
|
|
|
2025
2090
|
${this.journalHtml()}
|
|
2026
2091
|
`;
|
|
2027
2092
|
if (this.responsive) this.renderFrameBar();
|
|
2093
|
+
this.renderInfo();
|
|
2028
2094
|
this.bindControls();
|
|
2029
2095
|
return;
|
|
2030
2096
|
}
|
|
@@ -2038,13 +2104,13 @@ var Uivisor = class {
|
|
|
2038
2104
|
<span class="uiv-mech">${st.record.styling.primaryMechanism}</span>
|
|
2039
2105
|
</div>
|
|
2040
2106
|
${this.dsIndicatorHtml()}
|
|
2041
|
-
${this.currentStylesHtml()}
|
|
2042
2107
|
${this.breakpointBarHtml()}
|
|
2043
2108
|
${this.targetHtml(st)}
|
|
2044
2109
|
${this.controlsHtml(this.context(this.selected))}
|
|
2045
2110
|
${this.journalHtml()}
|
|
2046
2111
|
`;
|
|
2047
2112
|
if (this.responsive) this.renderFrameBar();
|
|
2113
|
+
this.renderInfo();
|
|
2048
2114
|
this.bindControls();
|
|
2049
2115
|
}
|
|
2050
2116
|
/** Small indicator: how many design tokens were detected (or a hint if none). */
|
|
@@ -2103,19 +2169,22 @@ var Uivisor = class {
|
|
|
2103
2169
|
cache.set(el, out);
|
|
2104
2170
|
return out;
|
|
2105
2171
|
}
|
|
2106
|
-
/**
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2109
|
-
|
|
2110
|
-
if (this.isChanged(props)) return "
|
|
2111
|
-
if (this.inheritedFrom(props)) return "
|
|
2172
|
+
/** Editing state of a property set: edited (set at this breakpoint) · inherit
|
|
2173
|
+
* (cascaded from another breakpoint) · file (authored in CSS) · auto
|
|
2174
|
+
* (browser-computed / default, not set anywhere). */
|
|
2175
|
+
controlState(props) {
|
|
2176
|
+
if (this.isChanged(props)) return "edited";
|
|
2177
|
+
if (this.inheritedFrom(props)) return "inherit";
|
|
2112
2178
|
const inherit = props.some((p) => INHERITED_PROPS.has(p));
|
|
2113
|
-
return this.isAuthored(props, inherit) ? "
|
|
2179
|
+
return this.isAuthored(props, inherit) ? "file" : "auto";
|
|
2180
|
+
}
|
|
2181
|
+
controlStateClass(props) {
|
|
2182
|
+
return ` st-${this.controlState(props)}`;
|
|
2114
2183
|
}
|
|
2115
2184
|
/** A control's label, with an "inherited from {bp}" badge when the value cascaded. */
|
|
2116
2185
|
ctlLabel(label, props) {
|
|
2117
2186
|
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>` : "";
|
|
2187
|
+
const badge = from ? ` <span class="uiv-inh" title="inherited from ${escapeAttr(this.bpLabel(from))} \u2014 not set at this breakpoint">\u2923${escapeHtml(this.bpLabel(from))}</span>` : "";
|
|
2119
2188
|
return `<span class="clabel">${label}${badge}</span>`;
|
|
2120
2189
|
}
|
|
2121
2190
|
/** Is any of `props` authored in the project CSS? For inherited properties we
|
|
@@ -2130,12 +2199,11 @@ var Uivisor = class {
|
|
|
2130
2199
|
return false;
|
|
2131
2200
|
}
|
|
2132
2201
|
/**
|
|
2133
|
-
*
|
|
2134
|
-
*
|
|
2135
|
-
*
|
|
2136
|
-
* Comprehensive + context-aware (flex/grid/position rows only when relevant).
|
|
2202
|
+
* Comprehensive read-only readout of the element's ACTUAL current styles
|
|
2203
|
+
* (context-aware: flex/grid/position rows only when relevant). Rendered into the
|
|
2204
|
+
* floating info block at the corner, not the panel. Returns the `<div.uiv-rrow>` rows.
|
|
2137
2205
|
*/
|
|
2138
|
-
|
|
2206
|
+
styleRows() {
|
|
2139
2207
|
const el = this.selected;
|
|
2140
2208
|
if (!el) return "";
|
|
2141
2209
|
let cs;
|
|
@@ -2246,33 +2314,45 @@ var Uivisor = class {
|
|
|
2246
2314
|
if (z && z !== "auto") add("z-index", z, ["z-index"]);
|
|
2247
2315
|
const tr = g("transform");
|
|
2248
2316
|
if (tr && tr !== "none") add("transform", clip(tr, 22), ["transform"]);
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2317
|
+
return rows.map((r) => `<div class="uiv-rrow"><span class="uiv-rk">${r.k}</span><span class="uiv-rv">${r.v}</span></div>`).join("");
|
|
2318
|
+
}
|
|
2319
|
+
/** Floating read-only "all styles" block, docked at the bottom-right corner so it
|
|
2320
|
+
* doesn't take space in the panel. Shown only while an element is selected. */
|
|
2321
|
+
renderInfo() {
|
|
2322
|
+
const info = this.q(".uiv-info");
|
|
2323
|
+
if (!this.enabled || !this.selected) {
|
|
2324
|
+
info.classList.remove("show");
|
|
2325
|
+
info.innerHTML = "";
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
info.innerHTML = `<div class="uiv-info-h">all styles <span class="uiv-info-sub">computed</span></div><div class="uiv-readout">${this.styleRows()}</div>`;
|
|
2329
|
+
info.classList.add("show");
|
|
2330
|
+
}
|
|
2331
|
+
/** Display label for a breakpoint name — the unprefixed "base" scope reads "all"
|
|
2332
|
+
* (applies to every size by default); internal key stays "base". */
|
|
2333
|
+
bpLabel(name) {
|
|
2334
|
+
return name === "base" ? "all" : name;
|
|
2252
2335
|
}
|
|
2253
2336
|
/** A device icon for a breakpoint chip, by its threshold (size proxy). */
|
|
2254
2337
|
bpIcon(name) {
|
|
2255
2338
|
if (name === "live") return ICONS.live;
|
|
2256
|
-
|
|
2339
|
+
if (name === "base") return ICONS.all;
|
|
2340
|
+
const px2 = this.bpSystem().breakpoints.find((b) => b.name === name)?.minWidth ?? 0;
|
|
2257
2341
|
if (px2 < 768) return ICONS.phone;
|
|
2258
2342
|
if (px2 < 1024) return ICONS.tablet;
|
|
2259
2343
|
return ICONS.desktop;
|
|
2260
2344
|
}
|
|
2261
|
-
/** Breakpoint chips
|
|
2262
|
-
* the panel (Live
|
|
2345
|
+
/** Breakpoint chips with icons. Order: "all" (base, the default) → Live → each
|
|
2346
|
+
* project breakpoint. Reused by the panel (Live) and the over-frame bar. */
|
|
2263
2347
|
breakpointChipsHtml() {
|
|
2264
2348
|
const sys = this.bpSystem();
|
|
2265
|
-
const names = ["base", ...sys.breakpoints.map((b) => b.name)];
|
|
2266
2349
|
const frameBp = this.responsive ? activeBreakpoint(this.frameWidth, sys).name : null;
|
|
2267
2350
|
const winBp = currentBreakpoint(sys).name;
|
|
2268
|
-
const
|
|
2269
|
-
const
|
|
2270
|
-
const
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
return chip(n, active, sys.dir === "min" ? `\u2265 ${px2}px` : `\u2264 ${px2}px`);
|
|
2274
|
-
}).join("");
|
|
2275
|
-
return live + chips;
|
|
2351
|
+
const isActive = (n) => this.responsive ? n === frameBp : n === winBp;
|
|
2352
|
+
const chip = (n, on, title) => `<button class="uiv-chip${on ? " on" : ""}" data-bp="${n}" title="${escapeAttr(title)}">${this.bpIcon(n)}<span>${this.bpLabel(n)}</span></button>`;
|
|
2353
|
+
const all = chip("base", isActive("base"), "No breakpoint \u2014 applies to every size by default");
|
|
2354
|
+
const rest = sys.breakpoints.map((b) => chip(b.name, isActive(b.name), sys.dir === "min" ? `\u2265 ${b.minWidth}px` : `\u2264 ${b.minWidth}px`)).join("");
|
|
2355
|
+
return all + rest;
|
|
2276
2356
|
}
|
|
2277
2357
|
/** Panel breakpoint bar — shown only in Live mode (in responsive mode the bar
|
|
2278
2358
|
* lives over the virtual screen instead). */
|
|
@@ -2283,7 +2363,7 @@ var Uivisor = class {
|
|
|
2283
2363
|
const liveW = typeof window !== "undefined" ? window.innerWidth : 0;
|
|
2284
2364
|
const detected = sys.name === "detected" ? "" : " (defaults)";
|
|
2285
2365
|
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.`;
|
|
2366
|
+
const hint = `Live \u2014 window <b>${liveW}px</b> = <b>${this.bpLabel(winBp)}</b>. ${cascade} Click a size to shrink the screen.`;
|
|
2287
2367
|
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
2368
|
}
|
|
2289
2369
|
/** Populate the bar over the virtual screen (responsive mode) with the chips. */
|
|
@@ -2328,8 +2408,19 @@ var Uivisor = class {
|
|
|
2328
2408
|
const secs = SECTIONS.map((sec) => {
|
|
2329
2409
|
const controls = sec.controls.filter((c) => this.relevant(c, ctx));
|
|
2330
2410
|
if (!controls.length) return "";
|
|
2331
|
-
|
|
2332
|
-
|
|
2411
|
+
if (this.collapsedSecs.has(sec.title)) return `<div class="uiv-sec">${this.accordionTitle(sec.title)}</div>`;
|
|
2412
|
+
const rows = [];
|
|
2413
|
+
const adds = [];
|
|
2414
|
+
for (const c of controls) {
|
|
2415
|
+
const css = c.css;
|
|
2416
|
+
if (c.kind === "len" && c.hideWhenAuto && css && !this.revealedCtls.has(css) && this.controlState([css]) === "auto") {
|
|
2417
|
+
adds.push(`<button class="uiv-addctl" data-css="${css}">+ ${escapeHtml(c.label)}</button>`);
|
|
2418
|
+
} else {
|
|
2419
|
+
rows.push(this.controlRow(c));
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
const addRow = adds.length ? `<div class="uiv-adds">${adds.join("")}</div>` : "";
|
|
2423
|
+
return `<div class="uiv-sec">${this.accordionTitle(sec.title)}${rows.join("")}${addRow}</div>`;
|
|
2333
2424
|
}).join("");
|
|
2334
2425
|
return legend + secs;
|
|
2335
2426
|
}
|
|
@@ -2458,7 +2549,10 @@ var Uivisor = class {
|
|
|
2458
2549
|
}
|
|
2459
2550
|
if (c.kind === "len") {
|
|
2460
2551
|
const v = this.liveNum(c.css);
|
|
2461
|
-
|
|
2552
|
+
const auto = c.hideWhenAuto && this.controlState([c.css]) === "auto";
|
|
2553
|
+
const value = auto || v == null ? "" : String(round2(v));
|
|
2554
|
+
const placeholder = auto && v != null ? `${round2(v)} (auto)` : "\u2014";
|
|
2555
|
+
return `<div class="uiv-ctl${this.controlStateClass([c.css])}">${this.ctlLabel(c.label, [c.css])}<div class="cfield">${this.numField(c.css, value, c.icon, this.isChanged([c.css]), false, placeholder)}</div><span></span></div>` + this.tokenRowHtml(c.css, "Token");
|
|
2462
2556
|
}
|
|
2463
2557
|
if (c.kind === "dim") {
|
|
2464
2558
|
return this.dimField(c);
|
|
@@ -2556,24 +2650,22 @@ var Uivisor = class {
|
|
|
2556
2650
|
this.renderBody();
|
|
2557
2651
|
});
|
|
2558
2652
|
});
|
|
2653
|
+
root.querySelectorAll(".uiv-addctl").forEach((node) => {
|
|
2654
|
+
const btn = node;
|
|
2655
|
+
const css = btn.getAttribute("data-css");
|
|
2656
|
+
btn.addEventListener("click", () => {
|
|
2657
|
+
this.revealedCtls.add(css);
|
|
2658
|
+
this.renderBody();
|
|
2659
|
+
});
|
|
2660
|
+
});
|
|
2559
2661
|
root.querySelectorAll(".uiv-chip").forEach((node) => {
|
|
2560
2662
|
const btn = node;
|
|
2561
2663
|
const bp = btn.getAttribute("data-bp");
|
|
2562
2664
|
btn.addEventListener("click", () => {
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
}
|
|
2568
|
-
const w = bp === "base" ? 390 : this.bpSystem().breakpoints.find((b) => b.name === bp)?.minWidth ?? 768;
|
|
2569
|
-
this.frameWidth = w;
|
|
2570
|
-
if (!this.responsive) {
|
|
2571
|
-
this.toggleResponsive(true);
|
|
2572
|
-
} else {
|
|
2573
|
-
this.setFrameWidth(w);
|
|
2574
|
-
this.reapplyScope();
|
|
2575
|
-
this.renderBody();
|
|
2576
|
-
}
|
|
2665
|
+
const w = bp === "base" ? this.baseFrameWidth() : this.bpSystem().breakpoints.find((b) => b.name === bp)?.minWidth ?? 768;
|
|
2666
|
+
this.setFrameWidth(w);
|
|
2667
|
+
this.reapplyScope();
|
|
2668
|
+
this.renderBody();
|
|
2577
2669
|
});
|
|
2578
2670
|
});
|
|
2579
2671
|
root.querySelectorAll(".uiv-clschip").forEach((node) => {
|
|
@@ -2693,7 +2785,52 @@ var Uivisor = class {
|
|
|
2693
2785
|
}
|
|
2694
2786
|
// ---- actions ----
|
|
2695
2787
|
records() {
|
|
2696
|
-
return [...this.states.
|
|
2788
|
+
return [...this.states.entries()].filter(([, s]) => s.record.changes.length > 0).map(([el, s]) => ({ ...s.record, smells: this.detectSmells(el) }));
|
|
2789
|
+
}
|
|
2790
|
+
/**
|
|
2791
|
+
* Detect dead / redundant / contradictory CSS on an element so the prompt can ask
|
|
2792
|
+
* the agent to QUESTION it instead of blindly applying — e.g. flex-direction on a
|
|
2793
|
+
* non-flex element, offsets without positioning, a display:none node that may be
|
|
2794
|
+
* deletable. Only flags properties the project actually AUTHORED (matchedProps).
|
|
2795
|
+
*/
|
|
2796
|
+
detectSmells(el) {
|
|
2797
|
+
const out = [];
|
|
2798
|
+
try {
|
|
2799
|
+
const cs = getComputedStyle(el);
|
|
2800
|
+
const authored = this.matchedProps(el);
|
|
2801
|
+
const disp = cs.display;
|
|
2802
|
+
const flexGrid = /flex|grid/.test(disp);
|
|
2803
|
+
const parent = el.parentElement;
|
|
2804
|
+
const parentFlexGrid = parent ? /flex|grid/.test(getComputedStyle(parent).display) : false;
|
|
2805
|
+
const has = (p) => authored.has(p);
|
|
2806
|
+
if (disp === "none")
|
|
2807
|
+
out.push(
|
|
2808
|
+
`It computes to \`display:none\` \u2014 it renders nothing here. If it isn't toggled visible elsewhere, prefer DELETING the element and its now-dead styles/classes over shipping a permanently hidden node.`
|
|
2809
|
+
);
|
|
2810
|
+
if (cs.visibility === "hidden" || parseFloat(cs.opacity) === 0)
|
|
2811
|
+
out.push(`It's invisible (visibility/opacity 0) \u2014 question whether it's needed at all.`);
|
|
2812
|
+
if (!flexGrid) {
|
|
2813
|
+
for (const p of ["flex-direction", "justify-content", "align-items", "flex-wrap"])
|
|
2814
|
+
if (has(p))
|
|
2815
|
+
out.push(`\`${p}\` is set but \`display\` is \`${disp}\` (not flex/grid) \u2192 it has NO effect. Remove it, or the display is wrong \u2014 ask which.`);
|
|
2816
|
+
}
|
|
2817
|
+
if (!parentFlexGrid) {
|
|
2818
|
+
for (const p of ["flex-grow", "flex-shrink", "flex-basis", "align-self", "order"])
|
|
2819
|
+
if (has(p))
|
|
2820
|
+
out.push(`\`${p}\` is set but the PARENT isn't flex/grid \u2192 no effect. Remove it or fix the parent.`);
|
|
2821
|
+
}
|
|
2822
|
+
if (cs.position === "static") {
|
|
2823
|
+
for (const p of ["top", "right", "bottom", "left", "z-index"])
|
|
2824
|
+
if (has(p))
|
|
2825
|
+
out.push(`\`${p}\` is set but \`position\` is \`static\` \u2192 no effect. Add positioning or drop it.`);
|
|
2826
|
+
}
|
|
2827
|
+
if (disp === "inline") {
|
|
2828
|
+
for (const p of ["width", "height"])
|
|
2829
|
+
if (has(p)) out.push(`\`${p}\` on an \`inline\` element has no effect (needs inline-block/block).`);
|
|
2830
|
+
}
|
|
2831
|
+
} catch {
|
|
2832
|
+
}
|
|
2833
|
+
return [...new Set(out)].slice(0, 6);
|
|
2697
2834
|
}
|
|
2698
2835
|
async copyPrompt() {
|
|
2699
2836
|
const recs = this.records();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uivisor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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",
|