@rahmantech/litedesign 0.1.0 → 0.2.0
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/README.md +25 -10
- package/dist/extension/content.js +431 -28
- package/dist/extension/core.iife.js +190 -8
- package/dist/extension/sw.js +1 -0
- package/dist/index.js +85 -12
- package/package.json +3 -2
- package/dist/extension/content.js.map +0 -7
- package/dist/extension/popup.js.map +0 -7
- package/dist/extension/sw.js.map +0 -7
- package/dist/index.js.map +0 -7
|
@@ -396,6 +396,7 @@ var __litedesign = (() => {
|
|
|
396
396
|
selector: input.selector,
|
|
397
397
|
rule: input.rule,
|
|
398
398
|
severity: input.severity,
|
|
399
|
+
kind: input.kind,
|
|
399
400
|
actual: input.actual,
|
|
400
401
|
expected: input.expected,
|
|
401
402
|
autoFix: input.autoFix
|
|
@@ -435,6 +436,7 @@ var __litedesign = (() => {
|
|
|
435
436
|
selector: worst.node.id,
|
|
436
437
|
key: spec.edge,
|
|
437
438
|
severity: "P2",
|
|
439
|
+
kind: "candidate",
|
|
438
440
|
actual: `${Math.round(worst.value * 10) / 10}px`,
|
|
439
441
|
expected: `${Math.round(target.value * 10) / 10}px`,
|
|
440
442
|
autoFix: false
|
|
@@ -465,6 +467,7 @@ var __litedesign = (() => {
|
|
|
465
467
|
rule: "contrast",
|
|
466
468
|
selector: node.id,
|
|
467
469
|
severity: "P1",
|
|
470
|
+
kind: "violation",
|
|
468
471
|
actual: `${Math.round(ratio * 100) / 100}:1`,
|
|
469
472
|
expected: `>= ${minimum}:1`,
|
|
470
473
|
autoFix: false
|
|
@@ -513,6 +516,7 @@ var __litedesign = (() => {
|
|
|
513
516
|
selector: node.id,
|
|
514
517
|
key: "x",
|
|
515
518
|
severity: "P2",
|
|
519
|
+
kind: "violation",
|
|
516
520
|
actual: `${node.box.scrollW}px content in ${node.box.clientW}px`,
|
|
517
521
|
expected: "content fits horizontally",
|
|
518
522
|
autoFix: false
|
|
@@ -526,6 +530,7 @@ var __litedesign = (() => {
|
|
|
526
530
|
selector: node.id,
|
|
527
531
|
key: "y",
|
|
528
532
|
severity: "P2",
|
|
533
|
+
kind: "violation",
|
|
529
534
|
actual: `${node.box.scrollH}px content in ${node.box.clientH}px`,
|
|
530
535
|
expected: "content fits vertically",
|
|
531
536
|
autoFix: false
|
|
@@ -564,6 +569,7 @@ var __litedesign = (() => {
|
|
|
564
569
|
rule: "radius-consistency",
|
|
565
570
|
selector: node.id,
|
|
566
571
|
severity: "P3",
|
|
572
|
+
kind: "candidate",
|
|
567
573
|
actual: value,
|
|
568
574
|
expected: expectedValue,
|
|
569
575
|
autoFix: true
|
|
@@ -602,6 +608,7 @@ var __litedesign = (() => {
|
|
|
602
608
|
selector: gap.a,
|
|
603
609
|
key: `${gap.axis}:${gap.b}`,
|
|
604
610
|
severity: "P2",
|
|
611
|
+
kind: "candidate",
|
|
605
612
|
actual: `${actual}px`,
|
|
606
613
|
expected: `${Math.round(actual / SPACING_SCALE) * SPACING_SCALE}px`,
|
|
607
614
|
autoFix: true
|
|
@@ -756,7 +763,7 @@ var __litedesign = (() => {
|
|
|
756
763
|
}
|
|
757
764
|
|
|
758
765
|
// packages/core/src/render/layers.ts
|
|
759
|
-
var LAYERS = ["grid", "guide", "gap", "issue", "hover"];
|
|
766
|
+
var LAYERS = ["grid", "guide", "gap", "issue", "dim", "focus", "hover"];
|
|
760
767
|
var GUIDE_COLOR = {
|
|
761
768
|
left: "rgba(236, 72, 153, 0.55)",
|
|
762
769
|
right: "rgba(236, 72, 153, 0.55)",
|
|
@@ -785,16 +792,28 @@ var __litedesign = (() => {
|
|
|
785
792
|
}
|
|
786
793
|
return layers;
|
|
787
794
|
}
|
|
788
|
-
function
|
|
795
|
+
function gridBackground(step, axis) {
|
|
796
|
+
const line = "rgba(59, 130, 246, 0.16)";
|
|
797
|
+
const vertical = `repeating-linear-gradient(to right, ${line} 0 1px, transparent 1px ${step}px)`;
|
|
798
|
+
const horizontal = `repeating-linear-gradient(to bottom, ${line} 0 1px, transparent 1px ${step}px)`;
|
|
799
|
+
if (axis === "vertical") return vertical;
|
|
800
|
+
if (axis === "horizontal") return horizontal;
|
|
801
|
+
return `${vertical}, ${horizontal}`;
|
|
802
|
+
}
|
|
803
|
+
function drawGrid(layer, grid) {
|
|
789
804
|
layer.replaceChildren();
|
|
790
|
-
layer.style.backgroundImage =
|
|
805
|
+
layer.style.backgroundImage = gridBackground(grid.step, grid.axis);
|
|
806
|
+
}
|
|
807
|
+
function focusedGuides(graph, focus) {
|
|
808
|
+
if (focus === null) return graph.guides;
|
|
809
|
+
return graph.guides.filter((guide) => guide.members.includes(focus.id));
|
|
791
810
|
}
|
|
792
|
-
function drawGuides(layer, graph) {
|
|
811
|
+
function drawGuides(layer, graph, focus = null) {
|
|
793
812
|
layer.replaceChildren();
|
|
794
813
|
const byId = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
795
814
|
const scrollX = window.scrollX;
|
|
796
815
|
const scrollY = window.scrollY;
|
|
797
|
-
for (const guide of graph.
|
|
816
|
+
for (const guide of focusedGuides(graph, focus).slice(0, MAX_MARKERS)) {
|
|
798
817
|
const span = spanOf(guide, byId);
|
|
799
818
|
if (span === null) continue;
|
|
800
819
|
const color = GUIDE_COLOR[guide.edge];
|
|
@@ -901,6 +920,144 @@ var __litedesign = (() => {
|
|
|
901
920
|
}
|
|
902
921
|
var HOVER_BORDER = "rgba(59, 130, 246, 0.9)";
|
|
903
922
|
var HOVER_FILL = "rgba(59, 130, 246, 0.08)";
|
|
923
|
+
var FOCUS_DIM = "rgba(15, 23, 42, 0.55)";
|
|
924
|
+
var FOCUS_BORDER = "#3b82f6";
|
|
925
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
926
|
+
var MIN_RADIUS = 4;
|
|
927
|
+
var TANGENT_COLOR = "rgba(148, 163, 184, 0.9)";
|
|
928
|
+
function parseRadius(css, rect) {
|
|
929
|
+
const text = css.trim();
|
|
930
|
+
if (text === "" || text.includes("/")) return null;
|
|
931
|
+
const parts = text.split(/\s+/);
|
|
932
|
+
if (parts.length > 4) return null;
|
|
933
|
+
const values = [];
|
|
934
|
+
for (const part of parts) {
|
|
935
|
+
const resolved = part.endsWith("%") ? Math.min(rect.w, rect.h) * (Number.parseFloat(part) / 100) : Number.parseFloat(part);
|
|
936
|
+
if (!Number.isFinite(resolved) || resolved < 0) return null;
|
|
937
|
+
values.push(resolved);
|
|
938
|
+
}
|
|
939
|
+
const [first = 0, second = first, third = first, fourth = second] = values;
|
|
940
|
+
const clamp = (radius) => Math.min(radius, rect.w / 2, rect.h / 2);
|
|
941
|
+
return {
|
|
942
|
+
tl: clamp(first),
|
|
943
|
+
tr: clamp(second),
|
|
944
|
+
br: clamp(third),
|
|
945
|
+
bl: clamp(fourth)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function radiusGeometry(rect, radii) {
|
|
949
|
+
const specs = [
|
|
950
|
+
{ corner: "tl", r: radii.tl, x: rect.x, y: rect.y, sx: 1, sy: 1 },
|
|
951
|
+
{ corner: "tr", r: radii.tr, x: rect.x + rect.w, y: rect.y, sx: -1, sy: 1 },
|
|
952
|
+
{ corner: "br", r: radii.br, x: rect.x + rect.w, y: rect.y + rect.h, sx: -1, sy: -1 },
|
|
953
|
+
{ corner: "bl", r: radii.bl, x: rect.x, y: rect.y + rect.h, sx: 1, sy: -1 }
|
|
954
|
+
];
|
|
955
|
+
const marks = [];
|
|
956
|
+
for (const spec of specs) {
|
|
957
|
+
if (spec.r < MIN_RADIUS) continue;
|
|
958
|
+
const hx = spec.x + spec.sx * spec.r;
|
|
959
|
+
const hy = spec.y;
|
|
960
|
+
const vx = spec.x;
|
|
961
|
+
const vy = spec.y + spec.sy * spec.r;
|
|
962
|
+
const sweep = spec.sx * spec.sy === 1 ? 0 : 1;
|
|
963
|
+
marks.push({
|
|
964
|
+
corner: spec.corner,
|
|
965
|
+
radius: spec.r,
|
|
966
|
+
arc: `M ${hx} ${hy} A ${spec.r} ${spec.r} 0 0 ${sweep} ${vx} ${vy}`,
|
|
967
|
+
tangents: [
|
|
968
|
+
[spec.x, spec.y, hx, hy],
|
|
969
|
+
[spec.x, spec.y, vx, vy]
|
|
970
|
+
]
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return marks;
|
|
974
|
+
}
|
|
975
|
+
function createSubLayer(name, before) {
|
|
976
|
+
const layer = document.createElement("div");
|
|
977
|
+
layer.dataset.layer = name;
|
|
978
|
+
layer.style.cssText = `${BASE};inset:0`;
|
|
979
|
+
before.before(layer);
|
|
980
|
+
return layer;
|
|
981
|
+
}
|
|
982
|
+
var MARK_COLOR = "rgba(217, 119, 6, 0.95)";
|
|
983
|
+
function drawMarks(layer, marks, bySelector) {
|
|
984
|
+
layer.replaceChildren();
|
|
985
|
+
if (marks.length === 0) return;
|
|
986
|
+
const scrollX = window.scrollX;
|
|
987
|
+
const scrollY = window.scrollY;
|
|
988
|
+
for (const mark of marks) {
|
|
989
|
+
const node = bySelector.get(mark.selector);
|
|
990
|
+
if (node === void 0) continue;
|
|
991
|
+
const badge = box(
|
|
992
|
+
layer,
|
|
993
|
+
`left:${node.rect.x - scrollX}px;top:${node.rect.y - scrollY}px;transform:translateY(-100%);padding:2px 6px;border-radius:4px;background:${MARK_COLOR};color:#fff;font:10px/1.4 ui-monospace,monospace;white-space:nowrap`
|
|
994
|
+
);
|
|
995
|
+
badge.textContent = mark.text;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function drawRadius(layer, node) {
|
|
999
|
+
layer.replaceChildren();
|
|
1000
|
+
if (node === null) return;
|
|
1001
|
+
const radii = parseRadius(node.styles.borderRadius, node.rect);
|
|
1002
|
+
if (radii === null) return;
|
|
1003
|
+
const view = {
|
|
1004
|
+
x: node.rect.x - window.scrollX,
|
|
1005
|
+
y: node.rect.y - window.scrollY,
|
|
1006
|
+
w: node.rect.w,
|
|
1007
|
+
h: node.rect.h
|
|
1008
|
+
};
|
|
1009
|
+
const marks = radiusGeometry(view, radii);
|
|
1010
|
+
if (marks.length === 0) return;
|
|
1011
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
1012
|
+
svg.style.cssText = `${BASE};inset:0;width:100%;height:100%`;
|
|
1013
|
+
for (const mark of marks) {
|
|
1014
|
+
const arc = document.createElementNS(SVG_NS, "path");
|
|
1015
|
+
arc.setAttribute("d", mark.arc);
|
|
1016
|
+
arc.setAttribute("fill", "none");
|
|
1017
|
+
arc.setAttribute("stroke", GAP_COLOR);
|
|
1018
|
+
arc.setAttribute("stroke-width", "2");
|
|
1019
|
+
svg.append(arc);
|
|
1020
|
+
for (const [x1, y1, x2, y2] of mark.tangents) {
|
|
1021
|
+
const line = document.createElementNS(SVG_NS, "line");
|
|
1022
|
+
line.setAttribute("x1", String(x1));
|
|
1023
|
+
line.setAttribute("y1", String(y1));
|
|
1024
|
+
line.setAttribute("x2", String(x2));
|
|
1025
|
+
line.setAttribute("y2", String(y2));
|
|
1026
|
+
line.setAttribute("stroke", TANGENT_COLOR);
|
|
1027
|
+
line.setAttribute("stroke-width", "1");
|
|
1028
|
+
line.setAttribute("stroke-dasharray", "3 3");
|
|
1029
|
+
svg.append(line);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
layer.append(svg);
|
|
1033
|
+
const labeled = marks.every((mark) => mark.radius === marks[0]?.radius) ? marks.slice(0, 1) : marks;
|
|
1034
|
+
for (const mark of labeled) {
|
|
1035
|
+
const inset = mark.radius * 0.55;
|
|
1036
|
+
const x = mark.corner === "tr" || mark.corner === "br" ? view.x + view.w - inset : view.x + inset;
|
|
1037
|
+
const y = mark.corner === "br" || mark.corner === "bl" ? view.y + view.h - inset : view.y + inset;
|
|
1038
|
+
label(layer, `${round(mark.radius, 1)}px`, x, y);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function dimStyle(rect, scroll) {
|
|
1042
|
+
const left = rect.x - scroll.x;
|
|
1043
|
+
const top = rect.y - scroll.y;
|
|
1044
|
+
return `left:${left}px;top:${top}px;width:${rect.w}px;height:${rect.h}px;box-shadow:0 0 0 9999px ${FOCUS_DIM}`;
|
|
1045
|
+
}
|
|
1046
|
+
function outlineStyle(rect, scroll) {
|
|
1047
|
+
const left = rect.x - scroll.x;
|
|
1048
|
+
const top = rect.y - scroll.y;
|
|
1049
|
+
return `left:${left}px;top:${top}px;width:${rect.w}px;height:${rect.h}px;border:2px solid ${FOCUS_BORDER};border-radius:2px`;
|
|
1050
|
+
}
|
|
1051
|
+
function drawDim(layer, node) {
|
|
1052
|
+
layer.replaceChildren();
|
|
1053
|
+
if (node === null) return;
|
|
1054
|
+
box(layer, dimStyle(node.rect, { x: window.scrollX, y: window.scrollY }));
|
|
1055
|
+
}
|
|
1056
|
+
function drawFocus(layer, node) {
|
|
1057
|
+
layer.replaceChildren();
|
|
1058
|
+
if (node === null) return;
|
|
1059
|
+
box(layer, outlineStyle(node.rect, { x: window.scrollX, y: window.scrollY }));
|
|
1060
|
+
}
|
|
904
1061
|
function drawHover(layer, node) {
|
|
905
1062
|
layer.replaceChildren();
|
|
906
1063
|
if (node === null) return;
|
|
@@ -958,6 +1115,8 @@ var __litedesign = (() => {
|
|
|
958
1115
|
printStyle.textContent = "@media print { :host { display: none !important } }";
|
|
959
1116
|
shadow.append(printStyle);
|
|
960
1117
|
const layers = createLayers(shadow);
|
|
1118
|
+
const radiusLayer = createSubLayer("radius", layers.hover);
|
|
1119
|
+
const marksLayer = createSubLayer("marks", layers.hover);
|
|
961
1120
|
if (options.toolbar !== void 0) shadow.append(options.toolbar);
|
|
962
1121
|
document.documentElement.append(host);
|
|
963
1122
|
let current = graph;
|
|
@@ -965,17 +1124,27 @@ var __litedesign = (() => {
|
|
|
965
1124
|
let hovered = null;
|
|
966
1125
|
let hoverTarget = null;
|
|
967
1126
|
let hoverFrame = 0;
|
|
1127
|
+
let focusId = null;
|
|
968
1128
|
let visible = new Set(options.layers ?? LAYERS);
|
|
1129
|
+
let marks = options.marks ?? [];
|
|
1130
|
+
let grid = options.grid ?? { step: GRID_STEP, axis: "both" };
|
|
969
1131
|
let frame = 0;
|
|
970
1132
|
const draw = () => {
|
|
971
1133
|
frame = 0;
|
|
972
1134
|
for (const name of LAYERS) {
|
|
973
1135
|
layers[name].style.display = visible.has(name) ? "block" : "none";
|
|
974
1136
|
}
|
|
975
|
-
drawGrid(layers.grid);
|
|
976
|
-
drawGuides(layers.guide, current);
|
|
1137
|
+
drawGrid(layers.grid, grid);
|
|
977
1138
|
drawGaps(layers.gap, current);
|
|
978
1139
|
drawIssues(layers.issue, current);
|
|
1140
|
+
const focused = focusId === null ? null : byId.get(focusId) ?? null;
|
|
1141
|
+
drawGuides(layers.guide, current, focused);
|
|
1142
|
+
const outlineNode = visible.has("focus") ? focused : null;
|
|
1143
|
+
const dimNode = visible.has("dim") ? focused : null;
|
|
1144
|
+
drawDim(layers.dim, dimNode);
|
|
1145
|
+
drawFocus(layers.focus, outlineNode);
|
|
1146
|
+
drawRadius(radiusLayer, outlineNode);
|
|
1147
|
+
drawMarks(marksLayer, marks, new Map(current.nodes.map((node) => [node.selector, node])));
|
|
979
1148
|
drawHover(layers.hover, hovered === null ? null : byId.get(hovered) ?? null);
|
|
980
1149
|
};
|
|
981
1150
|
const schedule = () => {
|
|
@@ -1020,12 +1189,25 @@ var __litedesign = (() => {
|
|
|
1020
1189
|
update(next) {
|
|
1021
1190
|
current = next;
|
|
1022
1191
|
byId = indexNodes(next);
|
|
1192
|
+
if (focusId !== null && !byId.has(focusId)) focusId = null;
|
|
1023
1193
|
draw();
|
|
1024
1194
|
},
|
|
1025
1195
|
setLayers(names) {
|
|
1026
1196
|
visible = new Set(names);
|
|
1027
1197
|
draw();
|
|
1028
1198
|
},
|
|
1199
|
+
setFocus(id) {
|
|
1200
|
+
focusId = id;
|
|
1201
|
+
draw();
|
|
1202
|
+
},
|
|
1203
|
+
setMarks(next) {
|
|
1204
|
+
marks = next;
|
|
1205
|
+
draw();
|
|
1206
|
+
},
|
|
1207
|
+
setGrid(next) {
|
|
1208
|
+
grid = next;
|
|
1209
|
+
draw();
|
|
1210
|
+
},
|
|
1029
1211
|
destroy() {
|
|
1030
1212
|
if (frame !== 0) cancelAnimationFrame(frame);
|
|
1031
1213
|
if (hoverFrame !== 0) cancelAnimationFrame(hoverFrame);
|
|
@@ -1042,7 +1224,7 @@ var __litedesign = (() => {
|
|
|
1042
1224
|
}
|
|
1043
1225
|
|
|
1044
1226
|
// packages/core/src/index.ts
|
|
1045
|
-
var VERSION = "0.
|
|
1227
|
+
var VERSION = "0.2.0";
|
|
1046
1228
|
return __toCommonJS(index_exports);
|
|
1047
1229
|
})();
|
|
1048
1230
|
//# sourceMappingURL=core.iife.js.map
|
package/dist/extension/sw.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -371,7 +371,10 @@ var Hub = class _Hub {
|
|
|
371
371
|
const previous = this.#socketTab.get(socket);
|
|
372
372
|
if (previous !== void 0 && previous !== tabId) {
|
|
373
373
|
const old = this.#clients.get(previous);
|
|
374
|
-
if (old?.socket === socket)
|
|
374
|
+
if (old?.socket === socket) {
|
|
375
|
+
this.#clients.delete(previous);
|
|
376
|
+
this.#broadcast({ type: "page-gone", tabId: previous });
|
|
377
|
+
}
|
|
375
378
|
}
|
|
376
379
|
this.#clients.set(tabId, { ...page, socket });
|
|
377
380
|
this.#socketTab.set(socket, tabId);
|
|
@@ -1096,6 +1099,7 @@ function issue(input) {
|
|
|
1096
1099
|
selector: input.selector,
|
|
1097
1100
|
rule: input.rule,
|
|
1098
1101
|
severity: input.severity,
|
|
1102
|
+
kind: input.kind,
|
|
1099
1103
|
actual: input.actual,
|
|
1100
1104
|
expected: input.expected,
|
|
1101
1105
|
autoFix: input.autoFix
|
|
@@ -1133,6 +1137,7 @@ var alignmentNearMiss = defineRule("alignment-near-miss", (graph) => {
|
|
|
1133
1137
|
selector: worst.node.id,
|
|
1134
1138
|
key: spec.edge,
|
|
1135
1139
|
severity: "P2",
|
|
1140
|
+
kind: "candidate",
|
|
1136
1141
|
actual: `${Math.round(worst.value * 10) / 10}px`,
|
|
1137
1142
|
expected: `${Math.round(target.value * 10) / 10}px`,
|
|
1138
1143
|
autoFix: false
|
|
@@ -1161,6 +1166,7 @@ var contrastMinimum = defineRule("contrast", (graph) => {
|
|
|
1161
1166
|
rule: "contrast",
|
|
1162
1167
|
selector: node.id,
|
|
1163
1168
|
severity: "P1",
|
|
1169
|
+
kind: "violation",
|
|
1164
1170
|
actual: `${Math.round(ratio * 100) / 100}:1`,
|
|
1165
1171
|
expected: `>= ${minimum}:1`,
|
|
1166
1172
|
autoFix: false
|
|
@@ -1207,6 +1213,7 @@ var overflowClipped = defineRule("overflow", (graph) => {
|
|
|
1207
1213
|
selector: node.id,
|
|
1208
1214
|
key: "x",
|
|
1209
1215
|
severity: "P2",
|
|
1216
|
+
kind: "violation",
|
|
1210
1217
|
actual: `${node.box.scrollW}px content in ${node.box.clientW}px`,
|
|
1211
1218
|
expected: "content fits horizontally",
|
|
1212
1219
|
autoFix: false
|
|
@@ -1220,6 +1227,7 @@ var overflowClipped = defineRule("overflow", (graph) => {
|
|
|
1220
1227
|
selector: node.id,
|
|
1221
1228
|
key: "y",
|
|
1222
1229
|
severity: "P2",
|
|
1230
|
+
kind: "violation",
|
|
1223
1231
|
actual: `${node.box.scrollH}px content in ${node.box.clientH}px`,
|
|
1224
1232
|
expected: "content fits vertically",
|
|
1225
1233
|
autoFix: false
|
|
@@ -1256,6 +1264,7 @@ var radiusConsistency = defineRule("radius-consistency", (graph) => {
|
|
|
1256
1264
|
rule: "radius-consistency",
|
|
1257
1265
|
selector: node.id,
|
|
1258
1266
|
severity: "P3",
|
|
1267
|
+
kind: "candidate",
|
|
1259
1268
|
actual: value,
|
|
1260
1269
|
expected: expectedValue,
|
|
1261
1270
|
autoFix: true
|
|
@@ -1292,6 +1301,7 @@ var spacingScale = defineRule("spacing-scale", (graph) => {
|
|
|
1292
1301
|
selector: gap.a,
|
|
1293
1302
|
key: `${gap.axis}:${gap.b}`,
|
|
1294
1303
|
severity: "P2",
|
|
1304
|
+
kind: "candidate",
|
|
1295
1305
|
actual: `${actual}px`,
|
|
1296
1306
|
expected: `${Math.round(actual / SPACING_SCALE) * SPACING_SCALE}px`,
|
|
1297
1307
|
autoFix: true
|
|
@@ -1308,7 +1318,7 @@ var defaultRules = [
|
|
|
1308
1318
|
contrastMinimum
|
|
1309
1319
|
];
|
|
1310
1320
|
var RULE_IDS = defaultRules.map((rule) => rule.id);
|
|
1311
|
-
var LAYERS = ["grid", "guide", "gap", "issue", "hover"];
|
|
1321
|
+
var LAYERS = ["grid", "guide", "gap", "issue", "dim", "focus", "hover"];
|
|
1312
1322
|
|
|
1313
1323
|
// packages/cli/src/drivers/extension.ts
|
|
1314
1324
|
var ExtensionDriver = class {
|
|
@@ -1359,6 +1369,21 @@ var ExtensionDriver = class {
|
|
|
1359
1369
|
}
|
|
1360
1370
|
return result;
|
|
1361
1371
|
}
|
|
1372
|
+
async labels() {
|
|
1373
|
+
const result = await this.#request("labels");
|
|
1374
|
+
if (result === void 0) {
|
|
1375
|
+
throw new ToolError(
|
|
1376
|
+
"the extension did not report labels. Reload the litedesign extension at chrome://extensions, then try again."
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
const labels = [];
|
|
1380
|
+
for (const entry of result.labels ?? []) {
|
|
1381
|
+
if (typeof entry.text === "string" && typeof entry.selector === "string") {
|
|
1382
|
+
labels.push({ label: entry.text, selector: entry.selector });
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return labels;
|
|
1386
|
+
}
|
|
1362
1387
|
async close() {
|
|
1363
1388
|
}
|
|
1364
1389
|
#request(action, payload) {
|
|
@@ -1493,22 +1518,46 @@ var PlaywrightDriver = class {
|
|
|
1493
1518
|
);
|
|
1494
1519
|
return page.screenshot({ type: "png" });
|
|
1495
1520
|
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Labels are marks the human makes through the toolbar; a headless page has
|
|
1523
|
+
* no human in it, so there is nothing to read.
|
|
1524
|
+
*/
|
|
1525
|
+
async labels() {
|
|
1526
|
+
return [];
|
|
1527
|
+
}
|
|
1496
1528
|
async overlay(options) {
|
|
1497
1529
|
const page = await this.#requirePage();
|
|
1498
1530
|
await this.#inject(page);
|
|
1499
1531
|
const layers = options.enabled ? options.layers ?? [...LAYERS] : [];
|
|
1500
|
-
await page.evaluate(
|
|
1532
|
+
const result = await page.evaluate(
|
|
1501
1533
|
(input) => {
|
|
1502
1534
|
const target = window;
|
|
1503
1535
|
target.__litedesign_handle__?.destroy();
|
|
1504
1536
|
target.__litedesign_handle__ = void 0;
|
|
1505
|
-
if (!input.enabled)
|
|
1537
|
+
if (!input.enabled) {
|
|
1538
|
+
target.__litedesign_focus__ = null;
|
|
1539
|
+
return { enabled: false, layers: [], focus: null };
|
|
1540
|
+
}
|
|
1506
1541
|
const graph = target.__litedesign.buildGraph(document.body);
|
|
1507
|
-
|
|
1542
|
+
const handle = target.__litedesign.render(graph, { layers: input.layers });
|
|
1543
|
+
target.__litedesign_handle__ = handle;
|
|
1544
|
+
if (input.focus !== void 0) target.__litedesign_focus__ = input.focus;
|
|
1545
|
+
let focus = null;
|
|
1546
|
+
const wanted = input.focus !== void 0 ? input.focus : target.__litedesign_focus__ ?? null;
|
|
1547
|
+
if (wanted !== null) {
|
|
1548
|
+
const node = target.__litedesign.resolveNode(graph, wanted);
|
|
1549
|
+
if (node !== null) {
|
|
1550
|
+
handle.setFocus(node.id);
|
|
1551
|
+
focus = node.selector;
|
|
1552
|
+
const element = document.querySelector(node.selector);
|
|
1553
|
+
element?.scrollIntoView({ block: "center", inline: "center", behavior: "instant" });
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return { enabled: true, layers: input.layers, focus };
|
|
1508
1557
|
},
|
|
1509
|
-
{ enabled: options.enabled, layers }
|
|
1558
|
+
{ enabled: options.enabled, layers, focus: options.focus }
|
|
1510
1559
|
);
|
|
1511
|
-
return
|
|
1560
|
+
return result;
|
|
1512
1561
|
}
|
|
1513
1562
|
async close() {
|
|
1514
1563
|
await this.#context?.close().catch(() => void 0);
|
|
@@ -1747,6 +1796,7 @@ function issuePayload(issues, limit) {
|
|
|
1747
1796
|
selector: issue2.selector,
|
|
1748
1797
|
rule: issue2.rule,
|
|
1749
1798
|
severity: issue2.severity,
|
|
1799
|
+
kind: issue2.kind,
|
|
1750
1800
|
actual: issue2.actual,
|
|
1751
1801
|
expected: issue2.expected,
|
|
1752
1802
|
autoFix: issue2.autoFix
|
|
@@ -1833,6 +1883,16 @@ async function inspectTool(input) {
|
|
|
1833
1883
|
return imageResult(payload, image);
|
|
1834
1884
|
}
|
|
1835
1885
|
|
|
1886
|
+
// packages/cli/src/mcp/tools/labels.ts
|
|
1887
|
+
async function labelsTool() {
|
|
1888
|
+
const driver = requirePage();
|
|
1889
|
+
const labels = await driver.labels();
|
|
1890
|
+
return textResult({
|
|
1891
|
+
labels,
|
|
1892
|
+
count: labels.length
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1836
1896
|
// packages/cli/src/mcp/tools/measure.ts
|
|
1837
1897
|
async function measureTool(input) {
|
|
1838
1898
|
const measured = await requirePage().measure(input.selector);
|
|
@@ -1844,10 +1904,11 @@ async function overlayTool(input) {
|
|
|
1844
1904
|
const driver = requirePage();
|
|
1845
1905
|
const enabled = input.enabled !== false;
|
|
1846
1906
|
const layers = parseLayers(input.layers);
|
|
1847
|
-
const result = await driver.overlay({ enabled, layers });
|
|
1907
|
+
const result = await driver.overlay({ enabled, layers, focus: input.focus });
|
|
1848
1908
|
return textResult({
|
|
1849
1909
|
status: result.enabled ? "shown" : "hidden",
|
|
1850
|
-
layers: result.layers
|
|
1910
|
+
layers: result.layers,
|
|
1911
|
+
focus: result.focus
|
|
1851
1912
|
});
|
|
1852
1913
|
}
|
|
1853
1914
|
function parseLayers(requested) {
|
|
@@ -1968,7 +2029,7 @@ async function runMcpServer() {
|
|
|
1968
2029
|
"find_issues",
|
|
1969
2030
|
{
|
|
1970
2031
|
title: "Find issues",
|
|
1971
|
-
description:
|
|
2032
|
+
description: 'Run layout rules on the open page and return findings. Each finding carries kind: "violation" is a measured fact (overflow, contrast below WCAG); "candidate" is a heuristic judgment (spacing scale, near-miss alignment, radius consistency) that can be a false positive \u2014 verify it with measure before changing code. Call inspect first.',
|
|
1972
2033
|
inputSchema: {
|
|
1973
2034
|
rules: z.array(z.string()).optional().describe(`Rules to run. Default: all. Valid: ${RULE_IDS.join(", ")}`),
|
|
1974
2035
|
limit: z.number().int().min(1).max(200).optional().describe("Maximum issues to return (default 50)")
|
|
@@ -1980,14 +2041,26 @@ async function runMcpServer() {
|
|
|
1980
2041
|
"overlay",
|
|
1981
2042
|
{
|
|
1982
2043
|
title: "Overlay",
|
|
1983
|
-
description: "Show or hide the measurement overlay in the page the human is looking at: grid, guides, gap labels, and issue boxes. Use it to
|
|
2044
|
+
description: "Show or hide the measurement overlay in the page the human is looking at: grid, guides, gap labels, and issue boxes. Use focus to spotlight the component you are editing: the rest of the page dims, the target is outlined, and it scrolls to the viewport center \u2014 so the human sees exactly what you are changing. focus takes the same selector measure returns; null clears it. Set layers once at the start if you need specific ones; after that call with focus only \u2014 the human owns the layer toggles in the toolbar, and your calls must not override their choice. Call inspect first.",
|
|
1984
2045
|
inputSchema: {
|
|
1985
2046
|
enabled: z.boolean().optional().describe("false hides the overlay (default true)"),
|
|
1986
|
-
layers: z.array(z.string()).optional().describe(`Layers to draw. Default: all. Valid: ${LAYERS.join(", ")}`)
|
|
2047
|
+
layers: z.array(z.string()).optional().describe(`Layers to draw. Default: all. Valid: ${LAYERS.join(", ")}`),
|
|
2048
|
+
focus: z.string().nullish().describe(
|
|
2049
|
+
"Selector of the component to spotlight (same format as measure). null clears the focus. Omit to leave it unchanged. Focusing scrolls the page, which changes what a later screenshot captures."
|
|
2050
|
+
)
|
|
1987
2051
|
}
|
|
1988
2052
|
},
|
|
1989
2053
|
async (input) => guard(() => overlayTool(input))
|
|
1990
2054
|
);
|
|
2055
|
+
server.registerTool(
|
|
2056
|
+
"labels",
|
|
2057
|
+
{
|
|
2058
|
+
title: "Labels",
|
|
2059
|
+
description: "Read the names the human pinned onto parts of the page through the toolbar label button. Each entry maps a human name to a selector you can pass to measure, find_issues, or overlay focus. When the human refers to a part by name, look it up here instead of guessing a selector. Labels are the human's marks: read-only for you. Returns an empty list when nothing is pinned yet.",
|
|
2060
|
+
inputSchema: {}
|
|
2061
|
+
},
|
|
2062
|
+
async () => guard(() => labelsTool())
|
|
2063
|
+
);
|
|
1991
2064
|
server.registerTool(
|
|
1992
2065
|
"screenshot",
|
|
1993
2066
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rahmantech/litedesign",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Grid, gap, and alignment on the real web page, plus an MCP bridge for agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Muhammad Abdurrahman",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"!dist/**/*.map",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
16
|
"engines": {
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"zod": "^4.6.7"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@litedesign/core": "0.
|
|
25
|
+
"@litedesign/core": "0.2.0",
|
|
25
26
|
"@types/ws": "^8.18.2"
|
|
26
27
|
},
|
|
27
28
|
"publishConfig": {
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/observer.ts", "../src/types.ts", "../src/store.ts", "../src/toolbar.ts", "../src/boot.ts", "../src/bridge.ts", "../src/content.ts"],
|
|
4
|
-
"sourcesContent": ["export type ObserverOptions = {\n root: Element\n debounceMs?: number\n onChange(): void\n}\n\nexport type Observer = {\n stop(): void\n}\n\nconst DEFAULT_DEBOUNCE = 150\n\n/**\n * Rebuilds after the page changes. MutationObserver catches DOM edits,\n * ResizeObserver catches layout-only changes, and window resize catches\n * viewport reflow. All paths share one debounce so a burst of mutations\n * produces one measurement pass.\n */\nexport function observeChanges(options: ObserverOptions): Observer {\n const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const schedule = (): void => {\n if (timer !== undefined) clearTimeout(timer)\n\n timer = setTimeout(() => {\n timer = undefined\n options.onChange()\n }, debounceMs)\n }\n\n const mutation = new MutationObserver(schedule)\n mutation.observe(options.root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class', 'style', 'hidden'],\n })\n\n const resize = new ResizeObserver(schedule)\n resize.observe(options.root)\n\n const onResize = (): void => schedule()\n const onNavigate = (): void => schedule()\n\n window.addEventListener('resize', onResize)\n window.addEventListener('popstate', onNavigate)\n window.addEventListener('hashchange', onNavigate)\n\n return {\n stop(): void {\n if (timer !== undefined) clearTimeout(timer)\n mutation.disconnect()\n resize.disconnect()\n window.removeEventListener('resize', onResize)\n window.removeEventListener('popstate', onNavigate)\n window.removeEventListener('hashchange', onNavigate)\n },\n }\n}\n", "import type { Graph, GraphNode, LayerName, OverlayHandle, OverlayOptions } from '@litedesign/core'\n\nexport type { Graph, GraphNode, LayerName, OverlayHandle }\n\n/**\n * The injected core is a global, not an import. This interface is the only\n * thing the extension knows about it; everything else comes from `core/` types\n * so there is no second source of truth.\n */\nexport type Core = {\n buildGraph(root?: Element | null): Graph\n resolveNode(graph: Graph, selector: string): GraphNode | null\n render(graph: Graph, options?: OverlayOptions): OverlayHandle\n VERSION: string\n}\n\n/** Exhaustive by construction: adding a layer to core breaks this object. */\nconst LAYER_NAMES: Record<LayerName, true> = {\n grid: true,\n guide: true,\n gap: true,\n issue: true,\n hover: true,\n}\n\nexport const LAYERS = Object.keys(LAYER_NAMES) as LayerName[]\n\n/**\n * What the popup shows: whether the worker holds a socket to the bridge, on\n * which port, and which tab it currently measures.\n */\nexport type BridgeStatus = {\n bridge: boolean\n port: number\n page: { tabId: number; url: string; title: string } | null\n}\n\n/** Layers the toolbar can switch. Hover inspection stays on. */\nexport const TOGGLE_LAYERS = [\n 'grid',\n 'guide',\n 'gap',\n 'issue',\n] as const satisfies readonly LayerName[]\nexport type ToggleLayer = (typeof TOGGLE_LAYERS)[number]\n\ndeclare global {\n interface Window {\n __litedesign?: Core\n __litedesign_state__?: 'starting' | 'active'\n __litedesign_teardown__?: () => void\n /** Replaces the visible layers without remounting. */\n __litedesign_layers__?: (layers: LayerName[]) => void\n /** True once the content-script bridge is listening. */\n __litedesign_bridge__?: boolean\n /** Toggle the user overlay. The service worker sends `toggle` here. */\n __litedesign_toggle__?: () => void\n }\n}\n", "import { LAYERS, type LayerName } from './types.js'\n\nconst STORAGE_KEY = 'litedesign'\n\nconst DEFAULT_LAYERS: LayerName[] = [...LAYERS]\n\n/**\n * What survives a reload: whether the overlay was on, and which layers it\n * showed. The fixture server reloads the page on every save, so without this\n * the grid disappears each time a file changes.\n */\nexport type OverlayState = {\n enabled: boolean\n layers: LayerName[]\n}\n\nexport function defaultLayers(): LayerName[] {\n return [...DEFAULT_LAYERS]\n}\n\nexport async function loadState(): Promise<OverlayState> {\n if (!hasStorage()) return { enabled: false, layers: defaultLayers() }\n\n try {\n const stored = await chrome.storage.local.get(STORAGE_KEY)\n return parseState(stored[STORAGE_KEY])\n } catch (error) {\n // Preferences are a convenience; a storage failure must not hide the\n // overlay. The user sees defaults instead.\n console.warn('[litedesign] could not read preferences', error)\n return { enabled: false, layers: defaultLayers() }\n }\n}\n\nexport async function saveState(state: OverlayState): Promise<void> {\n if (!hasStorage()) return\n\n try {\n await chrome.storage.local.set({ [STORAGE_KEY]: state })\n } catch (error) {\n // The current session keeps working with in-memory state.\n console.warn('[litedesign] could not save preferences', error)\n }\n}\n\nfunction hasStorage(): boolean {\n return typeof chrome !== 'undefined' && chrome.storage?.local !== undefined\n}\n\nfunction parseState(value: unknown): OverlayState {\n if (typeof value !== 'object' || value === null) {\n return { enabled: false, layers: defaultLayers() }\n }\n\n const record = value as { enabled?: unknown; layers?: unknown }\n\n return {\n // A value written before this flag existed means \"not known to be on\".\n enabled: record.enabled === true,\n layers: parseLayers(record.layers),\n }\n}\n\nfunction parseLayers(value: unknown): LayerName[] {\n if (!Array.isArray(value)) return defaultLayers()\n\n const valid = [...new Set(value.filter(isLayerName))]\n return valid.includes('hover') ? valid : [...valid, 'hover']\n}\n\nfunction isLayerName(value: unknown): value is LayerName {\n return typeof value === 'string' && (LAYERS as readonly string[]).includes(value)\n}\n", "import { type LayerName, TOGGLE_LAYERS, type ToggleLayer } from './types.js'\n\nexport type ToolbarOptions = {\n layers: LayerName[]\n onToggle(layer: ToggleLayer, enabled: boolean): void\n onClose(): void\n}\n\nexport type Toolbar = {\n element: HTMLElement\n setStats(nodes: number, issues: number, truncated: boolean): void\n setLayers(layers: LayerName[]): void\n}\n\nconst STYLE = `\n .bar {\n position: absolute;\n top: 12px;\n right: 12px;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n border-radius: 10px;\n background: rgba(17, 17, 17, 0.94);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);\n color: #e5e5e5;\n font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;\n pointer-events: auto;\n user-select: none;\n }\n .brand {\n padding: 0 2px;\n color: #ffffff;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n .stats {\n padding: 0 4px;\n color: #a3a3a3;\n white-space: nowrap;\n }\n button {\n padding: 5px 8px;\n border: 0;\n border-radius: 6px;\n background: #262626;\n color: #d4d4d4;\n font: inherit;\n cursor: pointer;\n }\n button:hover {\n background: #404040;\n }\n button.on {\n background: #3b82f6;\n color: #ffffff;\n }\n button[data-action='close'] {\n padding: 5px 7px;\n background: transparent;\n color: #a3a3a3;\n font-size: 14px;\n }\n button[data-action='close']:hover {\n background: #404040;\n color: #ffffff;\n }\n`\n\nexport function createToolbar(options: ToolbarOptions): Toolbar {\n const root = document.createElement('div')\n root.dataset.litedesignUi = ''\n\n const style = document.createElement('style')\n style.textContent = STYLE\n\n const bar = document.createElement('div')\n bar.className = 'bar'\n\n const brand = document.createElement('span')\n brand.className = 'brand'\n brand.textContent = 'litedesign'\n\n const stats = document.createElement('span')\n stats.className = 'stats'\n stats.textContent = 'measuring\u2026'\n\n let enabled = new Set<LayerName>(options.layers)\n const isEnabled = (layer: ToggleLayer): boolean => enabled.has(layer)\n\n bar.append(brand, stats)\n\n const buttons = new Map<ToggleLayer, HTMLButtonElement>()\n\n for (const layer of TOGGLE_LAYERS) {\n const button = document.createElement('button')\n button.type = 'button'\n button.dataset.layer = layer\n button.textContent = layer\n button.title = `Toggle ${layer} layer`\n button.addEventListener('click', () => options.onToggle(layer, !isEnabled(layer)))\n\n buttons.set(layer, button)\n bar.append(button)\n }\n\n const close = document.createElement('button')\n close.type = 'button'\n close.dataset.action = 'close'\n close.textContent = '\u00D7'\n close.title = 'Close litedesign'\n close.setAttribute('aria-label', 'Close litedesign')\n close.addEventListener('click', () => options.onClose())\n\n bar.append(close)\n root.append(style, bar)\n\n const setLayers = (layers: LayerName[]): void => {\n enabled = new Set(layers)\n\n for (const [layer, button] of buttons) {\n const on = enabled.has(layer)\n button.setAttribute('aria-pressed', String(on))\n button.classList.toggle('on', on)\n }\n }\n\n setLayers(options.layers)\n\n return {\n element: root,\n setStats(nodes: number, issues: number, truncated: boolean): void {\n stats.textContent = `${nodes} nodes \u00B7 ${issues} issues${truncated ? ' \u00B7 truncated' : ''}`\n },\n setLayers,\n }\n}\n", "import { type Observer, observeChanges } from './observer.js'\nimport { loadState, saveState } from './store.js'\nimport { createToolbar, type Toolbar } from './toolbar.js'\nimport type { Core, Graph, LayerName, OverlayHandle } from './types.js'\n\n/**\n * The host of the overlay that is on screen. The bridge hides it during a\n * capture instead of tearing it down, so the user's layer selection survives\n * the screenshot.\n */\nlet mountedHost: HTMLElement | undefined\n\nexport function overlayHost(): HTMLElement | undefined {\n return mountedHost\n}\n\n/**\n * Idempotent entry point. Chrome may inject the bundle again into the same tab,\n * and a fresh module scope forgets everything, so the state lives on `window`.\n *\n * - idle: mount\n * - starting: ignore the click, storage is still reading\n * - active: tear down\n */\nexport async function boot(core: Core): Promise<void> {\n if (window.__litedesign_state__ === 'active') {\n window.__litedesign_teardown__?.()\n return\n }\n\n if (window.__litedesign_state__ === 'starting') return\n\n window.__litedesign_state__ = 'starting'\n\n const state = await loadState()\n\n // The user may have clicked again while storage was reading.\n if (window.__litedesign_state__ !== 'starting') return\n\n try {\n mount(core, state.layers)\n } catch (error) {\n window.__litedesign_state__ = undefined\n throw error\n }\n}\n\n/**\n * MCP control. `boot` toggles because a click means \"flip it\"; this sets the\n * state, because the agent says exactly what it wants to see.\n *\n * Returns what the page is actually showing afterwards, so the tool can report\n * a reading instead of echoing the request.\n */\nexport async function applyOverlay(\n core: Core,\n options: { enabled: boolean; layers?: LayerName[] },\n): Promise<{ enabled: boolean; layers: LayerName[] }> {\n if (!options.enabled) {\n window.__litedesign_teardown__?.()\n return { enabled: false, layers: [] }\n }\n\n const layers = options.layers ?? (await loadState()).layers\n\n if (window.__litedesign_state__ === 'active') {\n window.__litedesign_layers__?.(layers)\n return { enabled: true, layers }\n }\n\n window.__litedesign_state__ = 'starting'\n\n try {\n mount(core, layers)\n } catch (error) {\n window.__litedesign_state__ = undefined\n throw error\n }\n\n return { enabled: overlayHost() !== undefined, layers }\n}\n\nfunction mount(core: Core, initial: LayerName[]): void {\n // `document.body` can be missing if the user clicks before parsing finishes;\n // documentElement is always there and catches the body once it appears.\n const root = document.body ?? document.documentElement\n\n let layers = initial\n let graph: Graph = core.buildGraph(root)\n let handle: OverlayHandle | undefined\n let observer: Observer | undefined\n let toolbar: Toolbar\n\n const teardown = (): void => {\n observer?.stop()\n handle?.destroy()\n handle = undefined\n observer = undefined\n mountedHost = undefined\n window.__litedesign_teardown__ = undefined\n window.__litedesign_layers__ = undefined\n window.__litedesign_state__ = undefined\n void saveState({ enabled: false, layers })\n }\n\n const refresh = (): void => {\n graph = core.buildGraph(root)\n handle?.update(graph)\n toolbar.setStats(graph.nodes.length, graph.issues.length, graph.truncated)\n }\n\n const setLayers = (next: LayerName[]): void => {\n layers = next\n handle?.setLayers(layers)\n toolbar.setLayers(layers)\n void saveState({ enabled: true, layers })\n }\n\n toolbar = createToolbar({\n layers,\n onToggle(layer, enabled): void {\n setLayers(\n enabled ? [...new Set([...layers, layer])] : layers.filter((name) => name !== layer),\n )\n },\n onClose: teardown,\n })\n\n toolbar.setStats(graph.nodes.length, graph.issues.length, graph.truncated)\n\n handle = core.render(graph, { layers, toolbar: toolbar.element })\n observer = observeChanges({ root, onChange: refresh })\n\n mountedHost = handle.host\n window.__litedesign_layers__ = setLayers\n window.__litedesign_teardown__ = teardown\n window.__litedesign_state__ = 'active'\n\n // A reload has to know the overlay was on, otherwise the next page load\n // starts clean and the grid vanishes on every save.\n void saveState({ enabled: true, layers })\n}\n", "import { applyOverlay, boot, overlayHost } from './boot.js'\nimport type { Core, Graph, LayerName, OverlayHandle } from './types.js'\n\ntype Payload = {\n selector?: string\n rules?: string[]\n layers?: LayerName[]\n fullPage?: boolean\n enabled?: boolean\n}\n\ntype Rect = { x: number; y: number; w: number; h: number }\n\ntype Prepared = { rect?: Rect; dpr: number }\n\ntype Response = { ok: true; result?: unknown } | { ok: false; error: string }\n\nconst TEMP_LAYERS: LayerName[] = ['grid', 'guide', 'gap', 'issue']\n\nconst FULL_PAGE =\n 'full-page screenshots need the Playwright driver. The extension can only capture the visible viewport.'\n\ntype Hidden = { element: HTMLElement; display: string }\n\n/**\n * While a capture is running the user's overlay is hidden. `handle` is the\n * temporary overlay for an annotated capture; `hidden` is the user's overlay\n * with the display value it had before the capture.\n */\nlet suspended: { handle?: OverlayHandle; hidden?: Hidden } | undefined\n\n/**\n * Installs the content-script side of the bridge. Safe to call on every\n * injection: Chrome may inject the bundle more than once into the same tab.\n */\nexport function installBridge(core: Core): void {\n window.__litedesign_toggle__ = () => void boot(core)\n\n if (window.__litedesign_bridge__ === true) return\n window.__litedesign_bridge__ = true\n\n // Tests inject this bundle into a normal page, where `chrome` does not exist.\n if (typeof chrome === 'undefined' || chrome.runtime?.onMessage === undefined) return\n\n chrome.runtime.onMessage.addListener((message: unknown, _sender, sendResponse) => {\n void respond(core, message).then(sendResponse, (error: unknown) => {\n sendResponse({ ok: false, error: messageOf(error) })\n })\n\n // Keep the channel open for the async response.\n return true\n })\n}\n\nasync function respond(core: Core, raw: unknown): Promise<Response> {\n if (typeof raw !== 'object' || raw === null) throw new Error('invalid message')\n\n const message = raw as { action?: unknown; payload?: unknown }\n const action = typeof message.action === 'string' ? message.action : ''\n const payload = (message.payload ?? {}) as Payload\n\n switch (action) {\n case 'ping':\n return { ok: true, result: true }\n case 'toggle':\n await boot(core)\n return { ok: true }\n case 'overlay':\n return {\n ok: true,\n result: await applyOverlay(core, {\n enabled: payload.enabled !== false,\n layers: payload.layers,\n }),\n }\n case 'graph':\n return { ok: true, result: core.buildGraph(root()) }\n case 'measure':\n return { ok: true, result: measure(core, payload.selector) }\n case 'screenshot_prepare':\n return { ok: true, result: await prepareScreenshot(core, payload) }\n case 'annotate_prepare':\n return { ok: true, result: await prepareAnnotation(core, payload) }\n case 'capture_cleanup':\n await cleanup()\n return { ok: true }\n default:\n throw new Error(`unknown action: ${action || '(none)'}`)\n }\n}\n\nfunction measure(core: Core, selector: string | undefined): unknown {\n if (selector === undefined || selector === '') throw new Error('measure needs a selector')\n\n const graph = core.buildGraph(root())\n const node = core.resolveNode(graph, selector)\n if (node === null) throw new Error(`selector \"${selector}\" did not match any element on the page`)\n\n const gaps = graph.gaps\n .filter((gap) => gap.a === node.id || gap.b === node.id)\n .map((gap) => ({\n to: gap.a === node.id ? gap.b : gap.a,\n axis: gap.axis,\n distance: gap.distance,\n kind: gap.kind,\n }))\n\n return {\n id: node.id,\n selector: node.selector,\n tag: node.tag,\n rect: node.rect,\n styles: node.styles,\n gaps,\n }\n}\n\nasync function prepareScreenshot(core: Core, payload: Payload): Promise<Prepared> {\n if (payload.fullPage === true) throw new Error(FULL_PAGE)\n\n const dpr = window.devicePixelRatio || 1\n const rect = await prepareElement(core, payload.selector)\n suspend()\n\n return { rect, dpr }\n}\n\nasync function prepareAnnotation(core: Core, payload: Payload): Promise<Prepared> {\n if (payload.fullPage === true) throw new Error(FULL_PAGE)\n\n const dpr = window.devicePixelRatio || 1\n const rect = await prepareElement(core, payload.selector)\n\n let graph: Graph = core.buildGraph(root())\n if (payload.rules !== undefined && payload.rules.length > 0) {\n const wanted = new Set(payload.rules)\n graph = { ...graph, issues: graph.issues.filter((issue) => wanted.has(issue.rule)) }\n }\n\n const handle = core.render(graph, { layers: payload.layers ?? TEMP_LAYERS })\n suspend(handle)\n await frames(2)\n\n return { rect, dpr }\n}\n\nasync function cleanup(): Promise<void> {\n const current = suspended\n if (current === undefined) return\n\n suspended = undefined\n current.handle?.destroy()\n showUserOverlay(current.hidden)\n}\n\n/**\n * Hides the user's overlay for the duration of a capture. It used to tear the\n * overlay down and remount it afterwards from storage, which meant a capture\n * could bring back a different layer selection than the one on screen. A\n * previous capture's overlay is replaced, not stacked.\n */\nfunction suspend(handle?: OverlayHandle): void {\n const previous = suspended\n previous?.handle?.destroy()\n showUserOverlay(previous?.hidden)\n\n suspended =\n handle === undefined ? { hidden: hideUserOverlay() } : { handle, hidden: hideUserOverlay() }\n}\n\nfunction hideUserOverlay(): Hidden | undefined {\n const element = overlayHost()\n if (element === undefined) return undefined\n\n const display = element.style.display\n element.style.display = 'none'\n\n return { element, display }\n}\n\nfunction showUserOverlay(hidden: Hidden | undefined): void {\n if (hidden !== undefined) hidden.element.style.display = hidden.display\n}\n\nasync function prepareElement(core: Core, selector: string | undefined): Promise<Rect | undefined> {\n if (selector === undefined || selector === '') return undefined\n\n const graph = core.buildGraph(root())\n const node = core.resolveNode(graph, selector)\n if (node === null) throw new Error(`selector \"${selector}\" did not match any element on the page`)\n\n let element: Element | null = null\n\n try {\n element = document.querySelector(node.selector)\n } catch {\n element = null\n }\n\n if (element === null) {\n throw new Error(\n `selector \"${selector}\" is inside a shadow root. Measure it, but capture the whole viewport instead.`,\n )\n }\n\n element.scrollIntoView({ block: 'center', inline: 'center' })\n await frames(1)\n\n const rect = element.getBoundingClientRect()\n return { x: rect.left, y: rect.top, w: rect.width, h: rect.height }\n}\n\nfunction root(): Element {\n return document.body ?? document.documentElement\n}\n\nfunction frames(count: number): Promise<void> {\n return new Promise((resolve) => {\n let left = count\n\n const tick = (): void => {\n left -= 1\n if (left <= 0) resolve()\n else requestAnimationFrame(tick)\n }\n\n requestAnimationFrame(tick)\n })\n}\n\nfunction messageOf(error: unknown): string {\n if (error instanceof Error && error.message !== '') return error.message\n if (typeof error === 'string' && error !== '') return error\n return 'unexpected error'\n}\n", "import { applyOverlay } from './boot.js'\nimport { installBridge } from './bridge.js'\nimport { loadState } from './store.js'\nimport type { Core } from './types.js'\n\nconst core: Core | undefined = window.__litedesign\n\nif (core === undefined) {\n console.warn('[litedesign] core not found; core.iife.js must be injected first')\n} else {\n installBridge(core)\n void restore(core)\n}\n\n/**\n * A reload drops the overlay with the rest of the DOM. If it was on when the\n * page went away, put it back, so a hot reload during an edit does not\n * silently clear the grid.\n */\nasync function restore(core: Core): Promise<void> {\n const state = await loadState()\n if (!state.enabled) return\n\n await applyOverlay(core, { enabled: true, layers: state.layers })\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["root", "core", "root", "core", "core"]
|
|
7
|
-
}
|