@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
package/README.md
CHANGED
|
@@ -44,21 +44,36 @@ litedesign mcp # MCP server (stdio)
|
|
|
44
44
|
|
|
45
45
|
## Tools
|
|
46
46
|
|
|
47
|
-
| Tool | What it returns
|
|
48
|
-
| ---------------------- |
|
|
49
|
-
| `capabilities` | version, drivers, mode, connected pages, valid rules
|
|
50
|
-
| `inspect` | tree summary; opens the page; no image by default
|
|
51
|
-
| `await_page` | wait for the page from `inspect` to connect
|
|
52
|
-
| `measure` | box model, computed styles, gaps for one selector
|
|
53
|
-
| `find_issues` |
|
|
54
|
-
| `screenshot` | PNG of the viewport or one element
|
|
55
|
-
| `screenshot_annotated` | PNG with grid, guides, gaps, and issue labels
|
|
56
|
-
| `overlay` | shows or hides
|
|
47
|
+
| Tool | What it returns |
|
|
48
|
+
| ---------------------- | -------------------------------------------------------- |
|
|
49
|
+
| `capabilities` | version, drivers, mode, connected pages, valid rules |
|
|
50
|
+
| `inspect` | tree summary; opens the page; no image by default |
|
|
51
|
+
| `await_page` | wait for the page from `inspect` to connect |
|
|
52
|
+
| `measure` | box model, computed styles, gaps for one selector |
|
|
53
|
+
| `find_issues` | findings with kind, selector, actual, expected, severity |
|
|
54
|
+
| `screenshot` | PNG of the viewport or one element |
|
|
55
|
+
| `screenshot_annotated` | PNG with grid, guides, gaps, and issue labels |
|
|
56
|
+
| `overlay` | shows or hides layers, spotlights one component |
|
|
57
|
+
| `labels` | names the human pinned in the toolbar, with selectors |
|
|
57
58
|
|
|
58
59
|
Call `inspect` first. It opens the page and keeps it alive for the other tools.
|
|
59
60
|
It returns no image unless you pass `screenshot: true`; use the `screenshot` or
|
|
60
61
|
`screenshot_annotated` tools when you actually need to see the page.
|
|
61
62
|
|
|
63
|
+
`overflow` and `contrast` findings are measured facts (`kind: "violation"`);
|
|
64
|
+
spacing, alignment, and radius findings are heuristics (`kind: "candidate"`)
|
|
65
|
+
that can be false positives. Verify a candidate with `measure` before changing
|
|
66
|
+
code.
|
|
67
|
+
|
|
68
|
+
`overlay` also takes `focus`: the same selector `measure` returns. The rest of
|
|
69
|
+
the page dims, the target is outlined and scrolled into view; `focus: null`
|
|
70
|
+
clears it. Focusing scrolls the page, so a capture made afterwards records the
|
|
71
|
+
scrolled viewport.
|
|
72
|
+
|
|
73
|
+
`labels` returns the names the human pinned onto parts of the page through the
|
|
74
|
+
toolbar. When the human refers to a part by name, look it up there instead of
|
|
75
|
+
guessing a selector. Labels are read-only for the agent.
|
|
76
|
+
|
|
62
77
|
## Drivers
|
|
63
78
|
|
|
64
79
|
The server prefers Playwright when `playwright-core` is installed. It launches
|
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
(() => {
|
|
3
|
+
// packages/extension/src/labels.ts
|
|
4
|
+
var STORE_KEY = "litedesign-labels";
|
|
5
|
+
function pageKey() {
|
|
6
|
+
return `${location.origin}${location.pathname}`;
|
|
7
|
+
}
|
|
8
|
+
function hasStorage() {
|
|
9
|
+
return typeof chrome !== "undefined" && chrome.storage?.local !== void 0;
|
|
10
|
+
}
|
|
11
|
+
function sanitize(value) {
|
|
12
|
+
if (!Array.isArray(value)) return [];
|
|
13
|
+
const labels = [];
|
|
14
|
+
for (const entry of value) {
|
|
15
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
16
|
+
const record = entry;
|
|
17
|
+
if (typeof record.id !== "string" || record.id === "") continue;
|
|
18
|
+
if (typeof record.text !== "string" || record.text.trim() === "") continue;
|
|
19
|
+
if (typeof record.selector !== "string" || record.selector === "") continue;
|
|
20
|
+
labels.push({ id: record.id, text: record.text, selector: record.selector });
|
|
21
|
+
}
|
|
22
|
+
return labels;
|
|
23
|
+
}
|
|
24
|
+
async function readStore() {
|
|
25
|
+
if (!hasStorage()) return {};
|
|
26
|
+
try {
|
|
27
|
+
const stored = await chrome.storage.local.get(STORE_KEY);
|
|
28
|
+
const pages = stored[STORE_KEY];
|
|
29
|
+
return typeof pages === "object" && pages !== null ? pages : {};
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.warn("[litedesign] could not read labels", error);
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function writeStore(store) {
|
|
36
|
+
if (!hasStorage()) return;
|
|
37
|
+
try {
|
|
38
|
+
await chrome.storage.local.set({ [STORE_KEY]: store });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn("[litedesign] could not save labels", error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function loadLabels() {
|
|
44
|
+
const store = await readStore();
|
|
45
|
+
return sanitize(store[pageKey()]);
|
|
46
|
+
}
|
|
47
|
+
async function replaceOnPage(next) {
|
|
48
|
+
const store = await readStore();
|
|
49
|
+
store[pageKey()] = next;
|
|
50
|
+
await writeStore(store);
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
async function addLabel(text, selector) {
|
|
54
|
+
const labels = await loadLabels();
|
|
55
|
+
const label = {
|
|
56
|
+
id: crypto.randomUUID(),
|
|
57
|
+
text: text.trim(),
|
|
58
|
+
selector
|
|
59
|
+
};
|
|
60
|
+
return replaceOnPage([...labels, label]);
|
|
61
|
+
}
|
|
62
|
+
async function removeLabel(id) {
|
|
63
|
+
const labels = await loadLabels();
|
|
64
|
+
return replaceOnPage(labels.filter((label) => label.id !== id));
|
|
65
|
+
}
|
|
66
|
+
async function renameLabel(id, text) {
|
|
67
|
+
const labels = await loadLabels();
|
|
68
|
+
return replaceOnPage(labels.map((label) => label.id === id ? { ...label, text } : label));
|
|
69
|
+
}
|
|
70
|
+
|
|
3
71
|
// packages/extension/src/observer.ts
|
|
4
72
|
var DEFAULT_DEBOUNCE = 150;
|
|
5
73
|
function observeChanges(options) {
|
|
@@ -44,6 +112,8 @@
|
|
|
44
112
|
guide: true,
|
|
45
113
|
gap: true,
|
|
46
114
|
issue: true,
|
|
115
|
+
dim: true,
|
|
116
|
+
focus: true,
|
|
47
117
|
hover: true
|
|
48
118
|
};
|
|
49
119
|
var LAYERS = Object.keys(LAYER_NAMES);
|
|
@@ -51,45 +121,61 @@
|
|
|
51
121
|
"grid",
|
|
52
122
|
"guide",
|
|
53
123
|
"gap",
|
|
54
|
-
"issue"
|
|
124
|
+
"issue",
|
|
125
|
+
"dim",
|
|
126
|
+
"focus"
|
|
55
127
|
];
|
|
128
|
+
var GRID_STEPS = [8, 12, 16, 24, 32, 48, 64, 96, 128];
|
|
56
129
|
|
|
57
130
|
// packages/extension/src/store.ts
|
|
58
131
|
var STORAGE_KEY = "litedesign";
|
|
59
132
|
var DEFAULT_LAYERS = [...LAYERS];
|
|
133
|
+
var DEFAULT_GRID = { step: 8, axis: "both" };
|
|
134
|
+
var AXES = ["vertical", "horizontal", "both"];
|
|
60
135
|
function defaultLayers() {
|
|
61
136
|
return [...DEFAULT_LAYERS];
|
|
62
137
|
}
|
|
63
138
|
async function loadState() {
|
|
64
|
-
if (!
|
|
139
|
+
if (!hasStorage2())
|
|
140
|
+
return { enabled: false, layers: defaultLayers(), focus: null, grid: DEFAULT_GRID };
|
|
65
141
|
try {
|
|
66
142
|
const stored = await chrome.storage.local.get(STORAGE_KEY);
|
|
67
143
|
return parseState(stored[STORAGE_KEY]);
|
|
68
144
|
} catch (error) {
|
|
69
145
|
console.warn("[litedesign] could not read preferences", error);
|
|
70
|
-
return { enabled: false, layers: defaultLayers() };
|
|
146
|
+
return { enabled: false, layers: defaultLayers(), focus: null, grid: DEFAULT_GRID };
|
|
71
147
|
}
|
|
72
148
|
}
|
|
73
149
|
async function saveState(state) {
|
|
74
|
-
if (!
|
|
150
|
+
if (!hasStorage2()) return;
|
|
75
151
|
try {
|
|
76
152
|
await chrome.storage.local.set({ [STORAGE_KEY]: state });
|
|
77
153
|
} catch (error) {
|
|
78
154
|
console.warn("[litedesign] could not save preferences", error);
|
|
79
155
|
}
|
|
80
156
|
}
|
|
81
|
-
function
|
|
157
|
+
function hasStorage2() {
|
|
82
158
|
return typeof chrome !== "undefined" && chrome.storage?.local !== void 0;
|
|
83
159
|
}
|
|
84
160
|
function parseState(value) {
|
|
85
161
|
if (typeof value !== "object" || value === null) {
|
|
86
|
-
return { enabled: false, layers: defaultLayers() };
|
|
162
|
+
return { enabled: false, layers: defaultLayers(), focus: null, grid: DEFAULT_GRID };
|
|
87
163
|
}
|
|
88
164
|
const record = value;
|
|
89
165
|
return {
|
|
90
166
|
// A value written before this flag existed means "not known to be on".
|
|
91
167
|
enabled: record.enabled === true,
|
|
92
|
-
layers: parseLayers(record.layers)
|
|
168
|
+
layers: parseLayers(record.layers),
|
|
169
|
+
focus: typeof record.focus === "string" && record.focus !== "" ? record.focus : null,
|
|
170
|
+
grid: parseGrid(record.grid)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function parseGrid(value) {
|
|
174
|
+
if (typeof value !== "object" || value === null) return DEFAULT_GRID;
|
|
175
|
+
const record = value;
|
|
176
|
+
return {
|
|
177
|
+
step: GRID_STEPS.includes(record.step) ? record.step : DEFAULT_GRID.step,
|
|
178
|
+
axis: AXES.includes(record.axis) ? record.axis : DEFAULT_GRID.axis
|
|
93
179
|
};
|
|
94
180
|
}
|
|
95
181
|
function parseLayers(value) {
|
|
@@ -156,7 +242,79 @@
|
|
|
156
242
|
background: #404040;
|
|
157
243
|
color: #ffffff;
|
|
158
244
|
}
|
|
245
|
+
.grid-group {
|
|
246
|
+
display: flex;
|
|
247
|
+
align-items: center;
|
|
248
|
+
gap: 2px;
|
|
249
|
+
padding-left: 6px;
|
|
250
|
+
margin-left: 2px;
|
|
251
|
+
border-left: 1px solid #404040;
|
|
252
|
+
}
|
|
253
|
+
.grid-group button {
|
|
254
|
+
padding: 4px 5px;
|
|
255
|
+
display: inline-flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
}
|
|
258
|
+
.grid-group svg {
|
|
259
|
+
display: block;
|
|
260
|
+
}
|
|
261
|
+
.grid-step {
|
|
262
|
+
padding: 0 3px;
|
|
263
|
+
color: #a3a3a3;
|
|
264
|
+
font-variant-numeric: tabular-nums;
|
|
265
|
+
min-width: 22px;
|
|
266
|
+
text-align: center;
|
|
267
|
+
}
|
|
268
|
+
.label-popup {
|
|
269
|
+
position: fixed;
|
|
270
|
+
box-sizing: border-box;
|
|
271
|
+
width: 164px;
|
|
272
|
+
padding: 4px;
|
|
273
|
+
border: 1px solid #d97706;
|
|
274
|
+
border-radius: 8px;
|
|
275
|
+
background: rgba(17, 17, 17, 0.96);
|
|
276
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
|
|
277
|
+
pointer-events: auto;
|
|
278
|
+
align-items: center;
|
|
279
|
+
gap: 4px;
|
|
280
|
+
display: none;
|
|
281
|
+
}
|
|
282
|
+
.label-popup input {
|
|
283
|
+
box-sizing: border-box;
|
|
284
|
+
flex: 1;
|
|
285
|
+
min-width: 0;
|
|
286
|
+
padding: 4px 6px;
|
|
287
|
+
border: 0;
|
|
288
|
+
border-radius: 5px;
|
|
289
|
+
background: #111111;
|
|
290
|
+
color: #e5e5e5;
|
|
291
|
+
font: inherit;
|
|
292
|
+
}
|
|
293
|
+
.label-popup button {
|
|
294
|
+
padding: 4px 7px;
|
|
295
|
+
border: 0;
|
|
296
|
+
border-radius: 5px;
|
|
297
|
+
background: #262626;
|
|
298
|
+
color: #d4d4d4;
|
|
299
|
+
font: inherit;
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
}
|
|
302
|
+
.label-popup button:hover {
|
|
303
|
+
background: #7f1d1d;
|
|
304
|
+
color: #ffffff;
|
|
305
|
+
}
|
|
159
306
|
`;
|
|
307
|
+
var LABEL_INPUT = { width: 164, height: 34 };
|
|
308
|
+
function placeInput(rect, viewport) {
|
|
309
|
+
const margin = 8;
|
|
310
|
+
const gap = 8;
|
|
311
|
+
const left = Math.min(
|
|
312
|
+
Math.max(rect.left, margin),
|
|
313
|
+
Math.max(margin, viewport.width - LABEL_INPUT.width - margin)
|
|
314
|
+
);
|
|
315
|
+
const above = rect.top > LABEL_INPUT.height + gap * 2;
|
|
316
|
+
return { left, top: above ? rect.top - gap : rect.bottom + gap, above };
|
|
317
|
+
}
|
|
160
318
|
function createToolbar(options) {
|
|
161
319
|
const root2 = document.createElement("div");
|
|
162
320
|
root2.dataset.litedesignUi = "";
|
|
@@ -173,6 +331,48 @@
|
|
|
173
331
|
let enabled = new Set(options.layers);
|
|
174
332
|
const isEnabled = (layer) => enabled.has(layer);
|
|
175
333
|
bar.append(brand, stats);
|
|
334
|
+
const labelButton = document.createElement("button");
|
|
335
|
+
labelButton.type = "button";
|
|
336
|
+
labelButton.dataset.action = "label";
|
|
337
|
+
labelButton.textContent = "label";
|
|
338
|
+
labelButton.title = "Mark a part of the page with a name the agent can read";
|
|
339
|
+
labelButton.addEventListener("click", () => options.onLabelToggle());
|
|
340
|
+
const popup = document.createElement("div");
|
|
341
|
+
popup.className = "label-popup";
|
|
342
|
+
const input = document.createElement("input");
|
|
343
|
+
input.type = "text";
|
|
344
|
+
input.placeholder = "label name";
|
|
345
|
+
const removeButton = document.createElement("button");
|
|
346
|
+
removeButton.type = "button";
|
|
347
|
+
removeButton.textContent = "\xD7";
|
|
348
|
+
removeButton.title = "Remove this label";
|
|
349
|
+
removeButton.hidden = true;
|
|
350
|
+
let committing = false;
|
|
351
|
+
let commit;
|
|
352
|
+
let remove;
|
|
353
|
+
const closeInput = () => {
|
|
354
|
+
popup.style.display = "none";
|
|
355
|
+
};
|
|
356
|
+
removeButton.addEventListener("mousedown", (event) => event.preventDefault());
|
|
357
|
+
removeButton.addEventListener("click", () => {
|
|
358
|
+
committing = true;
|
|
359
|
+
closeInput();
|
|
360
|
+
remove?.();
|
|
361
|
+
});
|
|
362
|
+
input.addEventListener("keydown", (event) => {
|
|
363
|
+
if (event.key === "Enter") {
|
|
364
|
+
committing = true;
|
|
365
|
+
const value = input.value;
|
|
366
|
+
closeInput();
|
|
367
|
+
commit?.(value);
|
|
368
|
+
} else if (event.key === "Escape") {
|
|
369
|
+
closeInput();
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
input.addEventListener("blur", () => {
|
|
373
|
+
if (!committing) closeInput();
|
|
374
|
+
committing = false;
|
|
375
|
+
});
|
|
176
376
|
const buttons = /* @__PURE__ */ new Map();
|
|
177
377
|
for (const layer of TOGGLE_LAYERS) {
|
|
178
378
|
const button = document.createElement("button");
|
|
@@ -184,6 +384,60 @@
|
|
|
184
384
|
buttons.set(layer, button);
|
|
185
385
|
bar.append(button);
|
|
186
386
|
}
|
|
387
|
+
let grid = options.grid;
|
|
388
|
+
const gridButtons = /* @__PURE__ */ new Map();
|
|
389
|
+
const line = (d) => `<svg width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="${d}"/></svg>`;
|
|
390
|
+
const axes = [
|
|
391
|
+
["vertical", "M3 1v10M9 1v10", "Grid: vertical lines only"],
|
|
392
|
+
["horizontal", "M1 3h10M1 9h10", "Grid: horizontal lines only"],
|
|
393
|
+
["both", "M3 1v10M9 1v10M1 3h10M1 9h10", "Grid: both directions"]
|
|
394
|
+
];
|
|
395
|
+
const gridGroup = document.createElement("span");
|
|
396
|
+
gridGroup.className = "grid-group";
|
|
397
|
+
for (const [axis, path, title] of axes) {
|
|
398
|
+
const button = document.createElement("button");
|
|
399
|
+
button.type = "button";
|
|
400
|
+
button.dataset.axis = axis;
|
|
401
|
+
button.title = title;
|
|
402
|
+
button.innerHTML = line(path);
|
|
403
|
+
button.addEventListener("click", () => {
|
|
404
|
+
grid = { ...grid, axis };
|
|
405
|
+
setGrid(grid);
|
|
406
|
+
options.onGrid(grid);
|
|
407
|
+
});
|
|
408
|
+
gridButtons.set(axis, button);
|
|
409
|
+
gridGroup.append(button);
|
|
410
|
+
}
|
|
411
|
+
const stepLabel = document.createElement("span");
|
|
412
|
+
stepLabel.className = "grid-step";
|
|
413
|
+
const stepDown = document.createElement("button");
|
|
414
|
+
stepDown.type = "button";
|
|
415
|
+
stepDown.dataset.axis = "step-down";
|
|
416
|
+
stepDown.textContent = "\u2212";
|
|
417
|
+
stepDown.title = "Smaller grid spacing";
|
|
418
|
+
stepDown.addEventListener("click", () => {
|
|
419
|
+
const index = GRID_STEPS.indexOf(grid.step);
|
|
420
|
+
if (index > 0) {
|
|
421
|
+
grid = { ...grid, step: GRID_STEPS[index - 1] ?? grid.step };
|
|
422
|
+
setGrid(grid);
|
|
423
|
+
options.onGrid(grid);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
const stepUp = document.createElement("button");
|
|
427
|
+
stepUp.type = "button";
|
|
428
|
+
stepUp.dataset.axis = "step-up";
|
|
429
|
+
stepUp.textContent = "+";
|
|
430
|
+
stepUp.title = "Larger grid spacing";
|
|
431
|
+
stepUp.addEventListener("click", () => {
|
|
432
|
+
const index = GRID_STEPS.indexOf(grid.step);
|
|
433
|
+
if (index !== -1 && index < GRID_STEPS.length - 1) {
|
|
434
|
+
grid = { ...grid, step: GRID_STEPS[index + 1] ?? grid.step };
|
|
435
|
+
setGrid(grid);
|
|
436
|
+
options.onGrid(grid);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
gridGroup.append(stepDown, stepLabel, stepUp);
|
|
440
|
+
bar.append(labelButton, gridGroup);
|
|
187
441
|
const close = document.createElement("button");
|
|
188
442
|
close.type = "button";
|
|
189
443
|
close.dataset.action = "close";
|
|
@@ -192,7 +446,8 @@
|
|
|
192
446
|
close.setAttribute("aria-label", "Close litedesign");
|
|
193
447
|
close.addEventListener("click", () => options.onClose());
|
|
194
448
|
bar.append(close);
|
|
195
|
-
|
|
449
|
+
popup.append(input, removeButton);
|
|
450
|
+
root2.append(style, bar, popup);
|
|
196
451
|
const setLayers = (layers) => {
|
|
197
452
|
enabled = new Set(layers);
|
|
198
453
|
for (const [layer, button] of buttons) {
|
|
@@ -201,13 +456,43 @@
|
|
|
201
456
|
button.classList.toggle("on", on);
|
|
202
457
|
}
|
|
203
458
|
};
|
|
459
|
+
const setGrid = (next) => {
|
|
460
|
+
grid = next;
|
|
461
|
+
for (const [axis, button] of gridButtons) {
|
|
462
|
+
const on = axis === grid.axis;
|
|
463
|
+
button.setAttribute("aria-pressed", String(on));
|
|
464
|
+
button.classList.toggle("on", on);
|
|
465
|
+
}
|
|
466
|
+
stepLabel.textContent = String(grid.step);
|
|
467
|
+
};
|
|
204
468
|
setLayers(options.layers);
|
|
469
|
+
setGrid(grid);
|
|
205
470
|
return {
|
|
206
471
|
element: root2,
|
|
207
472
|
setStats(nodes, issues, truncated) {
|
|
208
473
|
stats.textContent = `${nodes} nodes \xB7 ${issues} issues${truncated ? " \xB7 truncated" : ""}`;
|
|
209
474
|
},
|
|
210
|
-
setLayers
|
|
475
|
+
setLayers,
|
|
476
|
+
setGrid,
|
|
477
|
+
setLabelMode(on) {
|
|
478
|
+
labelButton.classList.toggle("on", on);
|
|
479
|
+
labelButton.setAttribute("aria-pressed", String(on));
|
|
480
|
+
if (!on) closeInput();
|
|
481
|
+
},
|
|
482
|
+
askLabel(rect, initial, fn, onRemove) {
|
|
483
|
+
commit = fn;
|
|
484
|
+
remove = onRemove;
|
|
485
|
+
committing = false;
|
|
486
|
+
const placement = placeInput(rect, { width: window.innerWidth, height: window.innerHeight });
|
|
487
|
+
popup.style.left = `${placement.left}px`;
|
|
488
|
+
popup.style.top = `${placement.top}px`;
|
|
489
|
+
popup.style.transform = placement.above ? "translateY(-100%)" : "none";
|
|
490
|
+
removeButton.hidden = onRemove === void 0;
|
|
491
|
+
popup.style.display = "flex";
|
|
492
|
+
input.value = initial;
|
|
493
|
+
input.focus();
|
|
494
|
+
if (initial !== "") input.select();
|
|
495
|
+
}
|
|
211
496
|
};
|
|
212
497
|
}
|
|
213
498
|
|
|
@@ -226,7 +511,7 @@
|
|
|
226
511
|
const state = await loadState();
|
|
227
512
|
if (window.__litedesign_state__ !== "starting") return;
|
|
228
513
|
try {
|
|
229
|
-
mount(core2, state.layers);
|
|
514
|
+
mount(core2, state.layers, state.grid);
|
|
230
515
|
} catch (error) {
|
|
231
516
|
window.__litedesign_state__ = void 0;
|
|
232
517
|
throw error;
|
|
@@ -235,28 +520,39 @@
|
|
|
235
520
|
async function applyOverlay(core2, options) {
|
|
236
521
|
if (!options.enabled) {
|
|
237
522
|
window.__litedesign_teardown__?.();
|
|
238
|
-
return { enabled: false, layers: [] };
|
|
523
|
+
return { enabled: false, layers: [], focus: null };
|
|
239
524
|
}
|
|
240
|
-
const
|
|
525
|
+
const stored = await loadState();
|
|
526
|
+
const layers = options.layers ?? stored.layers;
|
|
241
527
|
if (window.__litedesign_state__ === "active") {
|
|
242
528
|
window.__litedesign_layers__?.(layers);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
529
|
+
} else {
|
|
530
|
+
window.__litedesign_state__ = "starting";
|
|
531
|
+
try {
|
|
532
|
+
mount(core2, layers, stored.grid);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
window.__litedesign_state__ = void 0;
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
251
537
|
}
|
|
252
|
-
|
|
538
|
+
if (options.focus !== void 0) window.__litedesign_set_focus__?.(options.focus);
|
|
539
|
+
return {
|
|
540
|
+
enabled: overlayHost() !== void 0,
|
|
541
|
+
layers,
|
|
542
|
+
focus: window.__litedesign_focus_selector__?.() ?? null
|
|
543
|
+
};
|
|
253
544
|
}
|
|
254
|
-
function mount(core2, initial) {
|
|
545
|
+
function mount(core2, initial, initialGrid) {
|
|
255
546
|
const root2 = document.body ?? document.documentElement;
|
|
256
547
|
let layers = initial;
|
|
548
|
+
let grid = initialGrid;
|
|
257
549
|
let graph = core2.buildGraph(root2);
|
|
258
550
|
let handle;
|
|
259
551
|
let observer;
|
|
552
|
+
let focusSelector = null;
|
|
553
|
+
let focusId = null;
|
|
554
|
+
let labels = [];
|
|
555
|
+
let labelMode = false;
|
|
260
556
|
let toolbar;
|
|
261
557
|
const teardown = () => {
|
|
262
558
|
observer?.stop();
|
|
@@ -264,13 +560,24 @@
|
|
|
264
560
|
handle = void 0;
|
|
265
561
|
observer = void 0;
|
|
266
562
|
mountedHost = void 0;
|
|
563
|
+
focusSelector = null;
|
|
564
|
+
focusId = null;
|
|
565
|
+
if (labelMode) document.removeEventListener("click", onLabelClick, true);
|
|
566
|
+
labelMode = false;
|
|
267
567
|
window.__litedesign_teardown__ = void 0;
|
|
268
568
|
window.__litedesign_layers__ = void 0;
|
|
569
|
+
window.__litedesign_set_focus__ = void 0;
|
|
570
|
+
window.__litedesign_focus_selector__ = void 0;
|
|
269
571
|
window.__litedesign_state__ = void 0;
|
|
270
|
-
void saveState({ enabled: false, layers });
|
|
572
|
+
void saveState({ enabled: false, layers, focus: null, grid });
|
|
271
573
|
};
|
|
272
574
|
const refresh = () => {
|
|
273
575
|
graph = core2.buildGraph(root2);
|
|
576
|
+
if (focusId !== null && !graph.nodes.some((node) => node.id === focusId)) {
|
|
577
|
+
focusSelector = null;
|
|
578
|
+
focusId = null;
|
|
579
|
+
handle?.setFocus(null);
|
|
580
|
+
}
|
|
274
581
|
handle?.update(graph);
|
|
275
582
|
toolbar.setStats(graph.nodes.length, graph.issues.length, graph.truncated);
|
|
276
583
|
};
|
|
@@ -278,25 +585,118 @@
|
|
|
278
585
|
layers = next;
|
|
279
586
|
handle?.setLayers(layers);
|
|
280
587
|
toolbar.setLayers(layers);
|
|
281
|
-
void saveState({ enabled: true, layers });
|
|
588
|
+
void saveState({ enabled: true, layers, focus: focusSelector, grid });
|
|
589
|
+
};
|
|
590
|
+
const setGrid = (next) => {
|
|
591
|
+
grid = next;
|
|
592
|
+
handle?.setGrid(grid);
|
|
593
|
+
toolbar.setGrid(grid);
|
|
594
|
+
void saveState({ enabled: true, layers, focus: focusSelector, grid });
|
|
595
|
+
};
|
|
596
|
+
const setFocus = (selector) => {
|
|
597
|
+
if (selector === null) {
|
|
598
|
+
focusSelector = null;
|
|
599
|
+
focusId = null;
|
|
600
|
+
handle?.setFocus(null);
|
|
601
|
+
void saveState({ enabled: true, layers, focus: null, grid });
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const node = core2.resolveNode(graph, selector);
|
|
605
|
+
if (node === null) {
|
|
606
|
+
focusSelector = null;
|
|
607
|
+
focusId = null;
|
|
608
|
+
handle?.setFocus(null);
|
|
609
|
+
void saveState({ enabled: true, layers, focus: null, grid });
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
focusSelector = node.selector;
|
|
613
|
+
focusId = node.id;
|
|
614
|
+
handle?.setFocus(node.id);
|
|
615
|
+
let element = null;
|
|
616
|
+
try {
|
|
617
|
+
element = document.querySelector(node.selector);
|
|
618
|
+
} catch {
|
|
619
|
+
element = null;
|
|
620
|
+
}
|
|
621
|
+
element?.scrollIntoView({ block: "center", inline: "center", behavior: "instant" });
|
|
622
|
+
void saveState({ enabled: true, layers, focus: focusSelector, grid });
|
|
623
|
+
};
|
|
624
|
+
const applyMarks = () => {
|
|
625
|
+
handle?.setMarks(labels.map((label) => ({ selector: label.selector, text: label.text })));
|
|
626
|
+
};
|
|
627
|
+
const onLabelClick = (event) => {
|
|
628
|
+
const target = event.target;
|
|
629
|
+
if (!(target instanceof Element)) return;
|
|
630
|
+
if (target === handle?.host) return;
|
|
631
|
+
event.preventDefault();
|
|
632
|
+
event.stopPropagation();
|
|
633
|
+
const selector = core2.pathOf(target);
|
|
634
|
+
const existing = labels.find((label) => label.selector === selector);
|
|
635
|
+
if (existing !== void 0) {
|
|
636
|
+
toolbar.askLabel(
|
|
637
|
+
target.getBoundingClientRect(),
|
|
638
|
+
existing.text,
|
|
639
|
+
(text) => {
|
|
640
|
+
const trimmed = text.trim();
|
|
641
|
+
if (trimmed === "" || trimmed === existing.text) return;
|
|
642
|
+
void renameLabel(existing.id, trimmed).then((next) => {
|
|
643
|
+
labels = next;
|
|
644
|
+
applyMarks();
|
|
645
|
+
});
|
|
646
|
+
},
|
|
647
|
+
() => {
|
|
648
|
+
void removeLabel(existing.id).then((next) => {
|
|
649
|
+
labels = next;
|
|
650
|
+
applyMarks();
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
toolbar.askLabel(target.getBoundingClientRect(), "", (text) => {
|
|
657
|
+
const trimmed = text.trim();
|
|
658
|
+
if (trimmed === "") return;
|
|
659
|
+
void addLabel(trimmed, selector).then((next) => {
|
|
660
|
+
labels = next;
|
|
661
|
+
applyMarks();
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
};
|
|
665
|
+
const toggleLabelMode = () => {
|
|
666
|
+
labelMode = !labelMode;
|
|
667
|
+
toolbar.setLabelMode(labelMode);
|
|
668
|
+
if (labelMode) {
|
|
669
|
+
document.addEventListener("click", onLabelClick, true);
|
|
670
|
+
} else {
|
|
671
|
+
document.removeEventListener("click", onLabelClick, true);
|
|
672
|
+
}
|
|
282
673
|
};
|
|
283
674
|
toolbar = createToolbar({
|
|
284
675
|
layers,
|
|
676
|
+
grid,
|
|
285
677
|
onToggle(layer, enabled) {
|
|
286
678
|
setLayers(
|
|
287
679
|
enabled ? [.../* @__PURE__ */ new Set([...layers, layer])] : layers.filter((name) => name !== layer)
|
|
288
680
|
);
|
|
289
681
|
},
|
|
682
|
+
onLabelToggle: toggleLabelMode,
|
|
683
|
+
onGrid: setGrid,
|
|
290
684
|
onClose: teardown
|
|
291
685
|
});
|
|
292
686
|
toolbar.setStats(graph.nodes.length, graph.issues.length, graph.truncated);
|
|
293
|
-
handle = core2.render(graph, { layers, toolbar: toolbar.element });
|
|
687
|
+
handle = core2.render(graph, { layers, toolbar: toolbar.element, grid });
|
|
294
688
|
observer = observeChanges({ root: root2, onChange: refresh });
|
|
689
|
+
void loadLabels().then((next) => {
|
|
690
|
+
labels = next;
|
|
691
|
+
applyMarks();
|
|
692
|
+
});
|
|
295
693
|
mountedHost = handle.host;
|
|
296
694
|
window.__litedesign_layers__ = setLayers;
|
|
695
|
+
window.__litedesign_set_focus__ = setFocus;
|
|
696
|
+
window.__litedesign_focus_selector__ = () => focusSelector;
|
|
297
697
|
window.__litedesign_teardown__ = teardown;
|
|
298
698
|
window.__litedesign_state__ = "active";
|
|
299
|
-
void saveState({ enabled: true, layers });
|
|
699
|
+
void saveState({ enabled: true, layers, focus: focusSelector, grid });
|
|
300
700
|
}
|
|
301
701
|
|
|
302
702
|
// packages/extension/src/bridge.ts
|
|
@@ -331,9 +731,12 @@
|
|
|
331
731
|
ok: true,
|
|
332
732
|
result: await applyOverlay(core2, {
|
|
333
733
|
enabled: payload.enabled !== false,
|
|
334
|
-
layers: payload.layers
|
|
734
|
+
layers: payload.layers,
|
|
735
|
+
focus: payload.focus
|
|
335
736
|
})
|
|
336
737
|
};
|
|
738
|
+
case "labels":
|
|
739
|
+
return { ok: true, result: { labels: await loadLabels() } };
|
|
337
740
|
case "graph":
|
|
338
741
|
return { ok: true, result: core2.buildGraph(root()) };
|
|
339
742
|
case "measure":
|
|
@@ -465,7 +868,7 @@
|
|
|
465
868
|
async function restore(core2) {
|
|
466
869
|
const state = await loadState();
|
|
467
870
|
if (!state.enabled) return;
|
|
468
|
-
await applyOverlay(core2, { enabled: true, layers: state.layers });
|
|
871
|
+
await applyOverlay(core2, { enabled: true, layers: state.layers, focus: state.focus });
|
|
469
872
|
}
|
|
470
873
|
})();
|
|
471
874
|
//# sourceMappingURL=content.js.map
|