uivisor 0.1.10 → 0.2.1
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 +255 -48
- package/package.json +1 -1
package/dist/overlay/index.js
CHANGED
|
@@ -411,7 +411,8 @@ var ICONS = {
|
|
|
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
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
|
+
all: sv('<path d="M8 2.5v11 M3.2 5.2l9.6 5.6 M12.8 5.2l-9.6 5.6"/>'),
|
|
415
|
+
grip: '<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><circle cx="6" cy="4" r="1.05"/><circle cx="6" cy="8" r="1.05"/><circle cx="6" cy="12" r="1.05"/><circle cx="10" cy="4" r="1.05"/><circle cx="10" cy="8" r="1.05"/><circle cx="10" cy="12" r="1.05"/></svg>'
|
|
415
416
|
};
|
|
416
417
|
var SECTIONS = [
|
|
417
418
|
{
|
|
@@ -942,8 +943,9 @@ function renderPrompt(records) {
|
|
|
942
943
|
}
|
|
943
944
|
if (c.after.designToken || c.after.token && /^(text|bg|border|rounded|shadow)-(?!\[)/.test(c.after.token))
|
|
944
945
|
anyDesignToken = true;
|
|
946
|
+
const from = (c.before.computed ?? "").trim();
|
|
945
947
|
lines.push(
|
|
946
|
-
` - ${c.property}: ${c.
|
|
948
|
+
from ? ` - ${c.property}: ${from} \u2192 ${c.after.computed}${suggestion}` : ` - ${c.property}: set to ${c.after.computed}${suggestion}`
|
|
947
949
|
);
|
|
948
950
|
}
|
|
949
951
|
}
|
|
@@ -1189,24 +1191,34 @@ var CSS = (
|
|
|
1189
1191
|
|
|
1190
1192
|
.uiv-panel {
|
|
1191
1193
|
position: fixed; right: 16px; bottom: 72px; z-index: 2147483647;
|
|
1192
|
-
width:
|
|
1193
|
-
background: #
|
|
1194
|
-
border-radius:
|
|
1194
|
+
width: 360px; max-height: 84vh; overflow: auto;
|
|
1195
|
+
background: #141416; color: #e4e4e7; border: 1px solid #2a2a2e;
|
|
1196
|
+
border-radius: 14px; box-shadow: 0 16px 48px rgba(0,0,0,0.5);
|
|
1195
1197
|
font-size: 12px; display: none;
|
|
1196
1198
|
}
|
|
1197
1199
|
.uiv-panel.show { display: block; }
|
|
1198
1200
|
|
|
1199
1201
|
.uiv-head {
|
|
1200
|
-
display: flex; align-items: center; gap: 8px; padding:
|
|
1201
|
-
border-bottom: 1px solid #
|
|
1202
|
+
display: flex; align-items: center; gap: 8px; padding: 11px 13px;
|
|
1203
|
+
border-bottom: 1px solid #242428; position: sticky; top: 0; z-index: 5; background: #141416;
|
|
1202
1204
|
}
|
|
1203
|
-
.uiv-head b { font-size: 13px; color: #
|
|
1204
|
-
.uiv-bp { margin-left: auto; font-size: 10px; padding: 2px
|
|
1205
|
+
.uiv-head b { font-size: 13px; color: #fafafa; letter-spacing: .2px; }
|
|
1206
|
+
.uiv-bp { margin-left: auto; font-size: 10px; padding: 2px 8px; border-radius: 999px;
|
|
1205
1207
|
background: #312e81; color: #c7d2fe; font-weight: 600; }
|
|
1206
1208
|
.uiv-x { cursor: pointer; color: #71717a; padding: 2px 4px; }
|
|
1207
1209
|
.uiv-x:hover { color: #fff; }
|
|
1208
1210
|
|
|
1209
|
-
.uiv-sec { padding:
|
|
1211
|
+
.uiv-sec { padding: 11px 13px; border-bottom: 1px solid #1f1f22; }
|
|
1212
|
+
|
|
1213
|
+
/* ---- Framer-style top alignment toolbar ---- */
|
|
1214
|
+
.uiv-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 13px;
|
|
1215
|
+
border-bottom: 1px solid #1f1f22; }
|
|
1216
|
+
.uiv-tgroup { display: flex; gap: 2px; }
|
|
1217
|
+
.uiv-tbtn { display: flex; align-items: center; justify-content: center; width: 26px; height: 22px;
|
|
1218
|
+
border-radius: 5px; background: transparent; border: 1px solid transparent; color: #8b8b94; cursor: pointer; }
|
|
1219
|
+
.uiv-tbtn:hover { background: #1f1f23; color: #e4e4e7; }
|
|
1220
|
+
.uiv-tbtn.on { background: #312e81; border-color: #4f46e5; color: #fff; }
|
|
1221
|
+
.uiv-tsep { width: 1px; height: 16px; background: #2a2a2e; }
|
|
1210
1222
|
.uiv-empty { color: #71717a; padding: 18px 12px; text-align: center; }
|
|
1211
1223
|
|
|
1212
1224
|
.uiv-meta { line-height: 1.5; }
|
|
@@ -1318,8 +1330,8 @@ var CSS = (
|
|
|
1318
1330
|
.uiv-ctl > .cfield { min-width: 0; }
|
|
1319
1331
|
|
|
1320
1332
|
/* numeric field with a scrub handle on the left */
|
|
1321
|
-
.uiv-num { display: flex; align-items: stretch; background: #
|
|
1322
|
-
border: 1px solid #
|
|
1333
|
+
.uiv-num { display: flex; align-items: stretch; background: #1c1c20;
|
|
1334
|
+
border: 1px solid #313138; border-radius: 7px; overflow: hidden; }
|
|
1323
1335
|
.uiv-num.changed { border-color: #4ade80; }
|
|
1324
1336
|
.uiv-num.changed input { color: #4ade80; } /* uivisor-edited value \u2192 green */
|
|
1325
1337
|
.uiv-sel.changed, .uiv-color.changed { border-color: #4ade80; }
|
|
@@ -1344,7 +1356,7 @@ var CSS = (
|
|
|
1344
1356
|
|
|
1345
1357
|
.uiv-expand { display: flex; align-items: center; justify-content: center;
|
|
1346
1358
|
width: 26px; height: 28px; border-radius: 7px; cursor: pointer;
|
|
1347
|
-
background: #
|
|
1359
|
+
background: #1c1c20; border: 1px solid #313138; color: #8b8b94; }
|
|
1348
1360
|
.uiv-expand:hover { color: #fff; background: #3f3f46; }
|
|
1349
1361
|
.uiv-expand.on { color: #c7d2fe; border-color: #4f46e5; background: #312e81; }
|
|
1350
1362
|
|
|
@@ -1353,13 +1365,13 @@ var CSS = (
|
|
|
1353
1365
|
|
|
1354
1366
|
/* Weight dropdown only \u2014 must NOT match the unit <select> inside dim fields. */
|
|
1355
1367
|
.uiv-ctl select.uiv-sel {
|
|
1356
|
-
width: 100%; background: #
|
|
1368
|
+
width: 100%; background: #1c1c20; border: 1px solid #313138; color: #fff;
|
|
1357
1369
|
border-radius: 7px; padding: 6px 7px; font-size: 12px; outline: none;
|
|
1358
1370
|
}
|
|
1359
1371
|
.uiv-ctl select.uiv-sel:focus { border-color: #6366f1; }
|
|
1360
1372
|
.uiv-ctl input[type=color] { width: 100%; height: 28px; padding: 2px; cursor: pointer;
|
|
1361
|
-
background: #
|
|
1362
|
-
.uiv-ctl input.uiv-text { width: 100%; background: #
|
|
1373
|
+
background: #1c1c20; border: 1px solid #313138; border-radius: 7px; }
|
|
1374
|
+
.uiv-ctl input.uiv-text { width: 100%; background: #1c1c20; border: 1px solid #313138;
|
|
1363
1375
|
color: #fff; border-radius: 7px; padding: 6px 7px; font-size: 12px; outline: none; }
|
|
1364
1376
|
.uiv-ctl input.uiv-text:focus { border-color: #6366f1; }
|
|
1365
1377
|
.uiv-ctl input.uiv-text.changed { border-color: #4ade80; color: #4ade80; }
|
|
@@ -1383,7 +1395,7 @@ var CSS = (
|
|
|
1383
1395
|
.uiv-btn.primary:hover { background: #4338ca; }
|
|
1384
1396
|
.uiv-btn.ghost { flex: 0 0 auto; }
|
|
1385
1397
|
/* floating read-only "all styles" block, docked bottom-right, left of the panel */
|
|
1386
|
-
.uiv-info { position: fixed; right:
|
|
1398
|
+
.uiv-info { position: fixed; right: 384px; bottom: 16px; z-index: 2147483646;
|
|
1387
1399
|
width: 216px; max-height: 52vh; overflow: auto; display: none;
|
|
1388
1400
|
background: rgba(24,24,27,0.86); color: #e4e4e7;
|
|
1389
1401
|
border: 1px solid #3f3f46; border-radius: 10px; padding: 8px 10px;
|
|
@@ -1392,6 +1404,47 @@ var CSS = (
|
|
|
1392
1404
|
.uiv-info-h { font-size: 10px; text-transform: uppercase; letter-spacing: .4px;
|
|
1393
1405
|
color: #8b8b94; font-weight: 600; margin-bottom: 6px; }
|
|
1394
1406
|
.uiv-info-sub { color: #52525b; }
|
|
1407
|
+
/* ---- box-model widget (nested margin / padding, Figma/Framer style) ----
|
|
1408
|
+
Bands are sized so the side inputs NEVER overlap the inner content box:
|
|
1409
|
+
vertical band 26px (> input 18px), horizontal band 46px (> input 36px). */
|
|
1410
|
+
.uiv-bm { position: relative; height: 148px; margin: 2px 0 9px;
|
|
1411
|
+
background: #1b1b1f; border: 1px solid #2f2f35; border-radius: 9px; }
|
|
1412
|
+
.uiv-bm-pad { position: absolute; top: 26px; bottom: 26px; left: 46px; right: 46px;
|
|
1413
|
+
background: #26262c; border: 1px solid #3a3a42; border-radius: 7px; }
|
|
1414
|
+
.uiv-bm-content { position: absolute; z-index: 0; top: 26px; bottom: 26px; left: 46px; right: 46px;
|
|
1415
|
+
background: #34343c; border-radius: 5px; }
|
|
1416
|
+
.uiv-bm-tag { position: absolute; z-index: 1; top: 4px; left: 8px; font-size: 7.5px; font-weight: 700;
|
|
1417
|
+
letter-spacing: .5px; color: #6b6b73; pointer-events: none; }
|
|
1418
|
+
.uiv-bm-i { position: absolute; z-index: 3; width: 36px; height: 18px; padding: 0; box-sizing: border-box;
|
|
1419
|
+
background: #0e0e11; border: 1px solid #34343c; border-radius: 5px;
|
|
1420
|
+
color: #d4d4d8; text-align: center; text-align-last: center; line-height: 16px; font-size: 10px;
|
|
1421
|
+
outline: none; cursor: ew-resize; font-family: ui-monospace, monospace; -moz-appearance: textfield; }
|
|
1422
|
+
.uiv-bm-i::-webkit-outer-spin-button, .uiv-bm-i::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
1423
|
+
.uiv-bm-i:hover { border-color: #52525b; }
|
|
1424
|
+
.uiv-bm-i:focus { cursor: text; border-color: #6366f1; }
|
|
1425
|
+
.uiv-bm-i.bm-top { top: 4px; left: 50%; transform: translateX(-50%); }
|
|
1426
|
+
.uiv-bm-i.bm-bottom { bottom: 4px; left: 50%; transform: translateX(-50%); }
|
|
1427
|
+
.uiv-bm-i.bm-left { left: 5px; top: 50%; transform: translateY(-50%); }
|
|
1428
|
+
.uiv-bm-i.bm-right { right: 5px; top: 50%; transform: translateY(-50%); }
|
|
1429
|
+
.uiv-bm-i.st-file { color: #e4e4e7; }
|
|
1430
|
+
.uiv-bm-i.st-edited { color: #4ade80; }
|
|
1431
|
+
.uiv-bm-i.st-inherit { color: #38bdf8; }
|
|
1432
|
+
.uiv-bm-i.st-auto { color: #6b7280; }
|
|
1433
|
+
/* a side bound to a design token \u2014 shown by name, accent-coloured, no spin */
|
|
1434
|
+
.uiv-bm-i.uiv-bm-tok { color: #a5b4fc; font-size: 9px; letter-spacing: -0.2px;
|
|
1435
|
+
border-color: #4338ca; background: #1e1b4b40; text-overflow: ellipsis; }
|
|
1436
|
+
.uiv-bm-i.uiv-bm-tok.st-edited { color: #818cf8; }
|
|
1437
|
+
/* spacing-token dropdown \u2014 opens when a side value is focused/clicked */
|
|
1438
|
+
.uiv-bm-pop { display: flex; flex-wrap: wrap; gap: 5px; align-items: center;
|
|
1439
|
+
margin: -3px 0 8px; padding: 8px; background: #1c1c20; border: 1px solid #34343c;
|
|
1440
|
+
border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); }
|
|
1441
|
+
.uiv-bm-pop[hidden] { display: none; }
|
|
1442
|
+
.uiv-bm-poplabel { width: 100%; font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
1443
|
+
letter-spacing: .4px; color: #6b6b73; font-family: ui-monospace, monospace; }
|
|
1444
|
+
.uiv-bm-chip { background: #1e1b4b55; border: 1px solid #4338ca; color: #c7d2fe; border-radius: 6px;
|
|
1445
|
+
padding: 3px 8px; font-size: 10px; cursor: pointer; font-family: ui-monospace, monospace; }
|
|
1446
|
+
.uiv-bm-chip:hover { background: #4f46e5; border-color: #6366f1; color: #fff; }
|
|
1447
|
+
|
|
1395
1448
|
.uiv-toast { position: fixed; right: 16px; bottom: 128px; z-index: 2147483647;
|
|
1396
1449
|
background: #22c55e; color: #052e16; padding: 8px 12px; border-radius: 8px;
|
|
1397
1450
|
font-size: 12px; font-weight: 600; display: none; }
|
|
@@ -1422,6 +1475,8 @@ var Uivisor = class {
|
|
|
1422
1475
|
this.collapsedSecs = /* @__PURE__ */ new Set();
|
|
1423
1476
|
/** Controls manually revealed via "+" (hideWhenAuto controls that are auto). */
|
|
1424
1477
|
this.revealedCtls = /* @__PURE__ */ new Set();
|
|
1478
|
+
/** Box-model widget: the last-focused side the token dropdown targets. */
|
|
1479
|
+
this.lastBmSide = "padding-top";
|
|
1425
1480
|
/** Undo / redo stacks of full edit-state snapshots. */
|
|
1426
1481
|
this.undoStack = [];
|
|
1427
1482
|
this.redoStack = [];
|
|
@@ -1439,6 +1494,11 @@ var Uivisor = class {
|
|
|
1439
1494
|
// responsive (virtual screen) mode
|
|
1440
1495
|
this.responsive = false;
|
|
1441
1496
|
this.frameWidth = 768;
|
|
1497
|
+
/** The breakpoint new edits are scoped to — chosen explicitly via the chips,
|
|
1498
|
+
* NOT derived from the frame's pixel width. Defaults to 'base' so a casual tweak
|
|
1499
|
+
* applies to EVERY size (and never "disappears" when you view another size).
|
|
1500
|
+
* Picking a specific breakpoint chip narrows the scope to that breakpoint. */
|
|
1501
|
+
this.pickedBp = "base";
|
|
1442
1502
|
// ---- pointer handling ----
|
|
1443
1503
|
this.onMove = (e) => {
|
|
1444
1504
|
if (!this.enabled || this.isOurs(e)) {
|
|
@@ -1651,6 +1711,7 @@ var Uivisor = class {
|
|
|
1651
1711
|
} else {
|
|
1652
1712
|
this.scheduleBpRefresh();
|
|
1653
1713
|
if (!this.responsive) {
|
|
1714
|
+
this.pickedBp = "base";
|
|
1654
1715
|
this.frameWidth = this.defaultFrameWidth();
|
|
1655
1716
|
this.toggleResponsive(true);
|
|
1656
1717
|
}
|
|
@@ -1660,12 +1721,6 @@ var Uivisor = class {
|
|
|
1660
1721
|
defaultFrameWidth() {
|
|
1661
1722
|
return typeof window !== "undefined" ? window.innerWidth : 1280;
|
|
1662
1723
|
}
|
|
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
|
-
}
|
|
1669
1724
|
/** Stylesheets (esp. JIT/CDN Tailwind) load async — re-detect breakpoints a few
|
|
1670
1725
|
* times after enabling and re-render only if the set actually changed. */
|
|
1671
1726
|
scheduleBpRefresh() {
|
|
@@ -1760,8 +1815,7 @@ var Uivisor = class {
|
|
|
1760
1815
|
this.frameWidth = Math.max(280, Math.min(2400, Math.round(w)));
|
|
1761
1816
|
const host = this.q(".uiv-framehost");
|
|
1762
1817
|
host.style.width = `${this.frameWidth}px`;
|
|
1763
|
-
|
|
1764
|
-
this.q(".uiv-framew").textContent = `${this.frameWidth}px \xB7 ${this.bpLabel(bp)}`;
|
|
1818
|
+
this.q(".uiv-framew").textContent = `${this.frameWidth}px \xB7 editing ${this.bpLabel(this.scopeName())}`;
|
|
1765
1819
|
this.updateBp();
|
|
1766
1820
|
this.reposition();
|
|
1767
1821
|
}
|
|
@@ -1810,7 +1864,9 @@ var Uivisor = class {
|
|
|
1810
1864
|
};
|
|
1811
1865
|
this.states.set(el, {
|
|
1812
1866
|
record,
|
|
1813
|
-
|
|
1867
|
+
// Snapshot every registry property (not just the curated set) so a generic
|
|
1868
|
+
// edit's "before" value is known — the All-CSS inspector edits anything.
|
|
1869
|
+
original: snapshot(el, this.snapshotProps()),
|
|
1814
1870
|
applied: /* @__PURE__ */ new Set(),
|
|
1815
1871
|
dimUnit: {}
|
|
1816
1872
|
});
|
|
@@ -1819,6 +1875,10 @@ var Uivisor = class {
|
|
|
1819
1875
|
this.reposition();
|
|
1820
1876
|
this.renderBody();
|
|
1821
1877
|
}
|
|
1878
|
+
/** Properties to snapshot on selection — the curated set the controls can edit. */
|
|
1879
|
+
snapshotProps() {
|
|
1880
|
+
return ALL_CSS;
|
|
1881
|
+
}
|
|
1822
1882
|
// ---- value helpers ----
|
|
1823
1883
|
st() {
|
|
1824
1884
|
return this.selected ? this.states.get(this.selected) ?? null : null;
|
|
@@ -1851,6 +1911,14 @@ var Uivisor = class {
|
|
|
1851
1911
|
if (inline && !inline.includes("var(")) return inline;
|
|
1852
1912
|
return this.computedVal(css) || st.original[css] || "";
|
|
1853
1913
|
}
|
|
1914
|
+
/** If a design token is the effective value for `css`, its short name (e.g. "lg"
|
|
1915
|
+
* for --space-lg) — so the field shows the token by name, not its resolved px. */
|
|
1916
|
+
tokenNameFor(css) {
|
|
1917
|
+
const ch = this.effectiveChange(css);
|
|
1918
|
+
if (!ch?.after.designToken) return null;
|
|
1919
|
+
const t = this.designSystem().tokens.find((x) => x.cssVar === ch.after.designToken);
|
|
1920
|
+
return t ? t.name : ch.after.designToken;
|
|
1921
|
+
}
|
|
1854
1922
|
liveNum(css) {
|
|
1855
1923
|
const v = this.liveVal(css).trim();
|
|
1856
1924
|
const m = /^(-?\d*\.?\d+)px$/.exec(v);
|
|
@@ -1876,8 +1944,10 @@ var Uivisor = class {
|
|
|
1876
1944
|
}
|
|
1877
1945
|
selectCurrent(css) {
|
|
1878
1946
|
let v = this.liveVal(css).trim();
|
|
1879
|
-
if (
|
|
1880
|
-
|
|
1947
|
+
if (css === "font-weight") {
|
|
1948
|
+
if (v === "normal") v = "400";
|
|
1949
|
+
if (v === "bold") v = "700";
|
|
1950
|
+
}
|
|
1881
1951
|
return v;
|
|
1882
1952
|
}
|
|
1883
1953
|
// ---- apply / record / revert ----
|
|
@@ -1950,18 +2020,37 @@ var Uivisor = class {
|
|
|
1950
2020
|
}
|
|
1951
2021
|
this.reposition();
|
|
1952
2022
|
}
|
|
1953
|
-
/** The breakpoint
|
|
1954
|
-
|
|
1955
|
-
*
|
|
2023
|
+
/** The breakpoint new edits are scoped to. In responsive mode it's the EXPLICITLY
|
|
2024
|
+
* picked breakpoint (default 'base' = every size), decoupled from the frame's
|
|
2025
|
+
* pixel width — so editing at a desktop-sized frame still defaults to "all sizes"
|
|
2026
|
+
* instead of silently scoping to the top breakpoint and vanishing elsewhere. */
|
|
1956
2027
|
activeScope() {
|
|
1957
2028
|
const sys = this.bpSystem();
|
|
1958
|
-
if (this.responsive) return
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2029
|
+
if (!this.responsive) return currentBreakpoint(sys);
|
|
2030
|
+
const name = this.scopeName();
|
|
2031
|
+
const bp = sys.breakpoints.find((b) => b.name === name);
|
|
2032
|
+
return { name, minWidth: bp ? bp.minWidth : 0 };
|
|
2033
|
+
}
|
|
2034
|
+
/** The picked breakpoint, validated against the current system (falls back to
|
|
2035
|
+
* 'base' if a previously-picked breakpoint no longer exists after re-detection). */
|
|
2036
|
+
scopeName() {
|
|
2037
|
+
if (this.pickedBp === "base") return "base";
|
|
2038
|
+
return this.bpSystem().breakpoints.some((b) => b.name === this.pickedBp) ? this.pickedBp : "base";
|
|
2039
|
+
}
|
|
2040
|
+
/** The width the cascade is evaluated at — the representative width of the PICKED
|
|
2041
|
+
* scope, not the frame's visual width. base → a width where only base applies (so
|
|
2042
|
+
* base edits win and show everywhere); a named breakpoint → its threshold. */
|
|
1962
2043
|
activeWidth() {
|
|
1963
|
-
if (this.responsive) return
|
|
1964
|
-
return
|
|
2044
|
+
if (!this.responsive) return typeof window !== "undefined" ? window.innerWidth : 0;
|
|
2045
|
+
return this.scopeWidth(this.scopeName());
|
|
2046
|
+
}
|
|
2047
|
+
scopeWidth(name) {
|
|
2048
|
+
const sys = this.bpSystem();
|
|
2049
|
+
if (name !== "base") {
|
|
2050
|
+
const bp = sys.breakpoints.find((b) => b.name === name);
|
|
2051
|
+
if (bp) return bp.minWidth;
|
|
2052
|
+
}
|
|
2053
|
+
return sys.dir === "min" ? 0 : 1e6;
|
|
1965
2054
|
}
|
|
1966
2055
|
/** The recorded change that wins the breakpoint cascade for `css` at the active
|
|
1967
2056
|
* width — i.e. the value effective here, set on this breakpoint or inherited. */
|
|
@@ -2021,13 +2110,13 @@ var Uivisor = class {
|
|
|
2021
2110
|
const el = this.selected;
|
|
2022
2111
|
const st = this.st();
|
|
2023
2112
|
if (!el || !st) return;
|
|
2024
|
-
const
|
|
2113
|
+
const scope = this.activeScope();
|
|
2025
2114
|
for (const css of cssList) {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2115
|
+
st.record.changes = st.record.changes.filter(
|
|
2116
|
+
(c) => !(c.property === css && c.breakpoint === scope.name)
|
|
2117
|
+
);
|
|
2029
2118
|
}
|
|
2030
|
-
this.
|
|
2119
|
+
this.reapplyScope();
|
|
2031
2120
|
this.renderBody();
|
|
2032
2121
|
}
|
|
2033
2122
|
commitNumeric(cssList, raw) {
|
|
@@ -2103,6 +2192,7 @@ var Uivisor = class {
|
|
|
2103
2192
|
<div class="uiv-src">${escapeHtml(src)}</div>
|
|
2104
2193
|
<span class="uiv-mech">${st.record.styling.primaryMechanism}</span>
|
|
2105
2194
|
</div>
|
|
2195
|
+
${this.alignToolbarHtml()}
|
|
2106
2196
|
${this.dsIndicatorHtml()}
|
|
2107
2197
|
${this.breakpointBarHtml()}
|
|
2108
2198
|
${this.targetHtml(st)}
|
|
@@ -2346,9 +2436,8 @@ var Uivisor = class {
|
|
|
2346
2436
|
* project breakpoint. Reused by the panel (Live) and the over-frame bar. */
|
|
2347
2437
|
breakpointChipsHtml() {
|
|
2348
2438
|
const sys = this.bpSystem();
|
|
2349
|
-
const frameBp = this.responsive ? activeBreakpoint(this.frameWidth, sys).name : null;
|
|
2350
2439
|
const winBp = currentBreakpoint(sys).name;
|
|
2351
|
-
const isActive = (n) => this.responsive ? n ===
|
|
2440
|
+
const isActive = (n) => this.responsive ? n === this.scopeName() : n === winBp;
|
|
2352
2441
|
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
2442
|
const all = chip("base", isActive("base"), "No breakpoint \u2014 applies to every size by default");
|
|
2354
2443
|
const rest = sys.breakpoints.map((b) => chip(b.name, isActive(b.name), sys.dir === "min" ? `\u2265 ${b.minWidth}px` : `\u2264 ${b.minWidth}px`)).join("");
|
|
@@ -2403,15 +2492,60 @@ var Uivisor = class {
|
|
|
2403
2492
|
if (req === "flexgrid") return ctx.flexGrid;
|
|
2404
2493
|
return true;
|
|
2405
2494
|
}
|
|
2495
|
+
/** Framer-style top alignment toolbar — justify (horizontal) + align (vertical)
|
|
2496
|
+
* icon buttons. Shown for flex/grid containers (where they apply). */
|
|
2497
|
+
alignToolbarHtml() {
|
|
2498
|
+
const el = this.selected;
|
|
2499
|
+
if (!el || !this.context(el).flexGrid) return "";
|
|
2500
|
+
const j = this.liveVal("justify-content").trim();
|
|
2501
|
+
const a = this.liveVal("align-items").trim();
|
|
2502
|
+
const g = (r) => `<svg viewBox="0 0 14 14" width="13" height="13" fill="currentColor">${r}</svg>`;
|
|
2503
|
+
const JI = {
|
|
2504
|
+
"flex-start": g('<rect x="1" y="3" width="2" height="8"/><rect x="4" y="3" width="2" height="8"/>'),
|
|
2505
|
+
center: g('<rect x="4" y="3" width="2" height="8"/><rect x="8" y="3" width="2" height="8"/>'),
|
|
2506
|
+
"flex-end": g('<rect x="8" y="3" width="2" height="8"/><rect x="11" y="3" width="2" height="8"/>'),
|
|
2507
|
+
"space-between": g('<rect x="1" y="3" width="2" height="8"/><rect x="11" y="3" width="2" height="8"/>')
|
|
2508
|
+
};
|
|
2509
|
+
const AI = {
|
|
2510
|
+
"flex-start": g('<rect x="3" y="1" width="8" height="2"/><rect x="3" y="4" width="8" height="2"/>'),
|
|
2511
|
+
center: g('<rect x="3" y="4" width="8" height="2"/><rect x="3" y="8" width="8" height="2"/>'),
|
|
2512
|
+
"flex-end": g('<rect x="3" y="8" width="8" height="2"/><rect x="3" y="11" width="8" height="2"/>'),
|
|
2513
|
+
stretch: g('<rect x="3" y="1" width="8" height="12"/>')
|
|
2514
|
+
};
|
|
2515
|
+
const btn = (prop, val, icon, cur) => `<button class="uiv-tbtn${cur === val ? " on" : ""}" data-prop="${prop}" data-val="${val}" title="${prop}: ${val}">${icon}</button>`;
|
|
2516
|
+
const jb = Object.entries(JI).map(([v, ic]) => btn("justify-content", v, ic, j)).join("");
|
|
2517
|
+
const ab = Object.entries(AI).map(([v, ic]) => btn("align-items", v, ic, a)).join("");
|
|
2518
|
+
return `<div class="uiv-toolbar"><div class="uiv-tgroup">${jb}</div><div class="uiv-tsep"></div><div class="uiv-tgroup">${ab}</div></div>`;
|
|
2519
|
+
}
|
|
2520
|
+
/** Figma/Framer-style nested box-model widget: MARGIN ring around a PADDING ring,
|
|
2521
|
+
* with an editable number on each of the 8 sides. Commits via the engine. */
|
|
2522
|
+
boxModelHtml() {
|
|
2523
|
+
const num = (css) => {
|
|
2524
|
+
const n = this.liveNum(css);
|
|
2525
|
+
return n == null ? "0" : String(round2(n));
|
|
2526
|
+
};
|
|
2527
|
+
const side = (css, pos) => {
|
|
2528
|
+
const tok = this.tokenNameFor(css);
|
|
2529
|
+
const val = tok ?? num(css);
|
|
2530
|
+
const title = tok ? `${css}: ${tok}` : css;
|
|
2531
|
+
return `<input class="uiv-bm-i ${pos}${this.controlStateClass([css])}${tok ? " uiv-bm-tok" : ""}" data-css="${css}" value="${escapeAttr(val)}" title="${escapeAttr(title)}" inputmode="decimal" spellcheck="false">`;
|
|
2532
|
+
};
|
|
2533
|
+
const spaceTokens = this.designSystem().byCategory["spacing"] ?? [];
|
|
2534
|
+
const pop = spaceTokens.length ? `<div class="uiv-bm-pop" hidden><span class="uiv-bm-poplabel">Token</span>` + spaceTokens.map((t) => `<button class="uiv-bm-chip" data-var="${escapeAttr(t.cssVar)}" title="${escapeAttr(t.value)}">${escapeHtml(t.name)} \xB7 ${escapeHtml(t.value)}</button>`).join("") + `</div>` : "";
|
|
2535
|
+
return `<div class="uiv-bm"><span class="uiv-bm-tag">MARGIN</span>` + side("margin-top", "bm-top") + side("margin-right", "bm-right") + side("margin-bottom", "bm-bottom") + side("margin-left", "bm-left") + `<div class="uiv-bm-pad"><span class="uiv-bm-tag">PADDING</span>` + side("padding-top", "bm-top") + side("padding-right", "bm-right") + side("padding-bottom", "bm-bottom") + side("padding-left", "bm-left") + `<div class="uiv-bm-content"></div></div></div>` + pop;
|
|
2536
|
+
}
|
|
2406
2537
|
controlsHtml(ctx) {
|
|
2407
2538
|
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>`;
|
|
2408
2539
|
const secs = SECTIONS.map((sec) => {
|
|
2409
2540
|
const controls = sec.controls.filter((c) => this.relevant(c, ctx));
|
|
2410
2541
|
if (!controls.length) return "";
|
|
2411
2542
|
if (this.collapsedSecs.has(sec.title)) return `<div class="uiv-sec">${this.accordionTitle(sec.title)}</div>`;
|
|
2543
|
+
const isSpacing = sec.title === "Spacing";
|
|
2544
|
+
const widget = isSpacing ? this.boxModelHtml() : "";
|
|
2412
2545
|
const rows = [];
|
|
2413
2546
|
const adds = [];
|
|
2414
2547
|
for (const c of controls) {
|
|
2548
|
+
if (isSpacing && c.kind === "box" && (c.key === "padding" || c.key === "margin")) continue;
|
|
2415
2549
|
const css = c.css;
|
|
2416
2550
|
if (c.kind === "len" && c.hideWhenAuto && css && !this.revealedCtls.has(css) && this.controlState([css]) === "auto") {
|
|
2417
2551
|
adds.push(`<button class="uiv-addctl" data-css="${css}">+ ${escapeHtml(c.label)}</button>`);
|
|
@@ -2420,7 +2554,7 @@ var Uivisor = class {
|
|
|
2420
2554
|
}
|
|
2421
2555
|
}
|
|
2422
2556
|
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>`;
|
|
2557
|
+
return `<div class="uiv-sec">${this.accordionTitle(sec.title)}${widget}${rows.join("")}${addRow}</div>`;
|
|
2424
2558
|
}).join("");
|
|
2425
2559
|
return legend + secs;
|
|
2426
2560
|
}
|
|
@@ -2568,6 +2702,78 @@ var Uivisor = class {
|
|
|
2568
2702
|
}
|
|
2569
2703
|
bindControls() {
|
|
2570
2704
|
const root = this.root;
|
|
2705
|
+
root.querySelectorAll(".uiv-tbtn").forEach((node) => {
|
|
2706
|
+
const btn = node;
|
|
2707
|
+
const prop = btn.getAttribute("data-prop");
|
|
2708
|
+
const val = btn.getAttribute("data-val");
|
|
2709
|
+
btn.addEventListener("click", () => {
|
|
2710
|
+
this.pushHistory();
|
|
2711
|
+
this.commitValue([prop], val);
|
|
2712
|
+
});
|
|
2713
|
+
});
|
|
2714
|
+
const bmPop = root.querySelector(".uiv-bm-pop");
|
|
2715
|
+
const bmPopLabel = bmPop?.querySelector(".uiv-bm-poplabel");
|
|
2716
|
+
root.querySelectorAll(".uiv-bm-i").forEach((node) => {
|
|
2717
|
+
const inp = node;
|
|
2718
|
+
const css = inp.getAttribute("data-css");
|
|
2719
|
+
inp.addEventListener("focus", () => {
|
|
2720
|
+
this.lastBmSide = css;
|
|
2721
|
+
if (bmPop) {
|
|
2722
|
+
bmPop.hidden = false;
|
|
2723
|
+
if (bmPopLabel) bmPopLabel.textContent = `${css} \u2192`;
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
inp.addEventListener("blur", () => {
|
|
2727
|
+
window.setTimeout(() => {
|
|
2728
|
+
if (bmPop) bmPop.hidden = true;
|
|
2729
|
+
}, 140);
|
|
2730
|
+
});
|
|
2731
|
+
inp.addEventListener("change", () => {
|
|
2732
|
+
this.pushHistory();
|
|
2733
|
+
this.commitNumeric([css], inp.value);
|
|
2734
|
+
});
|
|
2735
|
+
inp.addEventListener("keydown", (e) => {
|
|
2736
|
+
if (e.key === "Enter") inp.blur();
|
|
2737
|
+
});
|
|
2738
|
+
inp.addEventListener("pointerdown", (e) => {
|
|
2739
|
+
const startX = e.clientX;
|
|
2740
|
+
const startVal = parseFloat(inp.value) || 0;
|
|
2741
|
+
let moved = false;
|
|
2742
|
+
let pushed = false;
|
|
2743
|
+
const move = (ev) => {
|
|
2744
|
+
if (!moved && Math.abs(ev.clientX - startX) < 3) return;
|
|
2745
|
+
if (!moved) {
|
|
2746
|
+
moved = true;
|
|
2747
|
+
inp.blur();
|
|
2748
|
+
}
|
|
2749
|
+
if (!pushed) {
|
|
2750
|
+
this.pushHistory();
|
|
2751
|
+
pushed = true;
|
|
2752
|
+
}
|
|
2753
|
+
let nv = startVal + Math.round(ev.clientX - startX);
|
|
2754
|
+
if (ev.shiftKey) nv = Math.round(nv / 10) * 10;
|
|
2755
|
+
nv = Math.max(0, nv);
|
|
2756
|
+
inp.value = String(nv);
|
|
2757
|
+
this.liveSet([css], `${nv}px`);
|
|
2758
|
+
};
|
|
2759
|
+
const up = () => {
|
|
2760
|
+
window.removeEventListener("pointermove", move);
|
|
2761
|
+
window.removeEventListener("pointerup", up);
|
|
2762
|
+
if (moved) this.recordProps([css]);
|
|
2763
|
+
};
|
|
2764
|
+
window.addEventListener("pointermove", move);
|
|
2765
|
+
window.addEventListener("pointerup", up);
|
|
2766
|
+
});
|
|
2767
|
+
});
|
|
2768
|
+
root.querySelectorAll(".uiv-bm-chip").forEach((node) => {
|
|
2769
|
+
const btn = node;
|
|
2770
|
+
const cssVar = btn.getAttribute("data-var");
|
|
2771
|
+
btn.addEventListener("mousedown", (e) => {
|
|
2772
|
+
e.preventDefault();
|
|
2773
|
+
const token = this.designSystem().tokens.find((t) => t.cssVar === cssVar);
|
|
2774
|
+
if (token) this.applyToken(this.lastBmSide, token);
|
|
2775
|
+
});
|
|
2776
|
+
});
|
|
2571
2777
|
root.querySelectorAll(".uiv-num:not(.uiv-dim)").forEach((node) => {
|
|
2572
2778
|
const box = node;
|
|
2573
2779
|
const cssList = (box.getAttribute("data-css") || "").split(",").filter(Boolean);
|
|
@@ -2602,6 +2808,7 @@ var Uivisor = class {
|
|
|
2602
2808
|
const input = node;
|
|
2603
2809
|
const css = input.getAttribute("data-css");
|
|
2604
2810
|
input.addEventListener("change", () => {
|
|
2811
|
+
if (input.value.toLowerCase() === toHexInput(this.liveVal(css)).toLowerCase()) return;
|
|
2605
2812
|
this.pushHistory();
|
|
2606
2813
|
this.commitValue([css], input.value);
|
|
2607
2814
|
});
|
|
@@ -2662,8 +2869,8 @@ var Uivisor = class {
|
|
|
2662
2869
|
const btn = node;
|
|
2663
2870
|
const bp = btn.getAttribute("data-bp");
|
|
2664
2871
|
btn.addEventListener("click", () => {
|
|
2665
|
-
|
|
2666
|
-
this.setFrameWidth(
|
|
2872
|
+
this.pickedBp = bp;
|
|
2873
|
+
this.setFrameWidth(bp === "base" ? this.defaultFrameWidth() : this.scopeWidth(bp));
|
|
2667
2874
|
this.reapplyScope();
|
|
2668
2875
|
this.renderBody();
|
|
2669
2876
|
});
|
|
@@ -2955,7 +3162,7 @@ function escapeHtml(s) {
|
|
|
2955
3162
|
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2956
3163
|
}
|
|
2957
3164
|
function escapeAttr(s) {
|
|
2958
|
-
return s.replace(/"/g, """);
|
|
3165
|
+
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
2959
3166
|
}
|
|
2960
3167
|
function cssAttrEscape(s) {
|
|
2961
3168
|
return s.replace(/["\\]/g, "\\$&");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uivisor",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|