gravity-lite 3.0.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/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/bridge.d.ts +12 -0
- package/dist/bridge.js +103 -0
- package/dist/bridge.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +73 -0
- package/dist/cli.js.map +1 -0
- package/dist/diagnostics.d.ts +67 -0
- package/dist/diagnostics.js +329 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/mcp-server.d.ts +18 -0
- package/dist/mcp-server.js +517 -0
- package/dist/mcp-server.js.map +1 -0
- package/extension/background.js +197 -0
- package/extension/icon128.svg +1 -0
- package/extension/icon16.svg +1 -0
- package/extension/icon48.svg +1 -0
- package/extension/manifest.json +24 -0
- package/extension/offscreen.html +4 -0
- package/extension/offscreen.js +74 -0
- package/extension/popup.html +105 -0
- package/extension/popup.js +84 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 DharuNamikaze
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# gravity-lite
|
|
2
|
+
|
|
3
|
+
AI-powered CSS layout diagnostics. Zero-config MCP server for AI assistants.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/gravity-lite)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Give your AI assistant (Kiro, Cursor, Claude, etc.) the ability to **see what actually renders** in your browser — not just read source code. Gravity bridges AI tools to live Chrome tabs via the Chrome DevTools Protocol.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g gravity-lite
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Requirements:** Node.js ≥ 16, Chrome ≥ 116
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Setup (one-time, ~2 minutes)
|
|
21
|
+
|
|
22
|
+
### 1. Add to your IDE MCP config
|
|
23
|
+
|
|
24
|
+
**Kiro** (`.kiro/settings/mcp.json`), **Cursor** (`~/.cursor/mcp.json`), or **Claude Desktop** (`claude_desktop_config.json`):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"gravity": { "command": "gravity" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Restart your IDE. The MCP server starts automatically.
|
|
35
|
+
|
|
36
|
+
### 2. Load the Chrome extension
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
chrome://extensions → enable Developer Mode → Load unpacked
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Point it at the `extension/` folder inside the package:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Print the exact path to load
|
|
46
|
+
gravity doctor
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Connect to a tab
|
|
50
|
+
|
|
51
|
+
Click the **Gravity** icon in Chrome → **Connect to Tab**.
|
|
52
|
+
|
|
53
|
+
Done. The extension connects to the MCP server automatically.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Tools
|
|
58
|
+
|
|
59
|
+
| Tool | What it does |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `connect_browser` | Check extension connection status |
|
|
62
|
+
| `diagnose_layout` | Detect overflow, hidden, offscreen, unresolved CSS vars |
|
|
63
|
+
| `inspect_stacking` | Debug z-index failures and stacking context traps |
|
|
64
|
+
| `check_accessibility` | WCAG contrast ratio, touch targets, ARIA tree |
|
|
65
|
+
| `inspect_responsive` | Fixed widths that break on mobile, viewport overflow |
|
|
66
|
+
| `debug_flexgrid` | Flexbox/Grid container + children deep analysis |
|
|
67
|
+
| `get_computed_layout` | Full computed style snapshot with CSS rule specificity |
|
|
68
|
+
| `highlight_element` | Color-coded overlay in the browser (content/padding/border/margin) |
|
|
69
|
+
| `screenshot_element` | Capture any element as a base64 PNG |
|
|
70
|
+
| `get_page_performance` | Layout thrash metrics, paint timings |
|
|
71
|
+
|
|
72
|
+
### Example prompts
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
diagnose the #header element
|
|
76
|
+
why is .modal behind everything — inspect stacking context
|
|
77
|
+
check accessibility of the .submit-btn
|
|
78
|
+
screenshot the #hero section
|
|
79
|
+
highlight .nav-bar for 5 seconds
|
|
80
|
+
debug the flex container .card-grid
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## How it works
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
IDE / AI ──stdio──► MCP Server (Node.js)
|
|
89
|
+
│
|
|
90
|
+
WS :9224 ◄── extension connects OUT to server
|
|
91
|
+
│ (no native messaging, no OS setup)
|
|
92
|
+
Chrome Extension (MV3)
|
|
93
|
+
├── offscreen.js — persistent WebSocket
|
|
94
|
+
└── background.js — chrome.debugger API
|
|
95
|
+
│
|
|
96
|
+
Active Tab (CDP)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**The key insight:** The extension connects *out* to the MCP server, not the other way around. No native messaging, no registry keys, no OS-specific setup. Works on Linux, macOS, and Windows.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Commands
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
gravity # start MCP server (called by IDE automatically)
|
|
107
|
+
gravity doctor # check setup status and print extension path
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Environment variables
|
|
111
|
+
|
|
112
|
+
| Variable | Default | Description |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `GRAVITY_PORT` | `9224` | WebSocket bridge port |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Troubleshooting
|
|
119
|
+
|
|
120
|
+
| Problem | Fix |
|
|
121
|
+
|---|---|
|
|
122
|
+
| "Browser extension not connected" | Open Chrome, load the extension, click "Connect to Tab" |
|
|
123
|
+
| "Port 9224 already in use" | Set `GRAVITY_PORT=9225` in your MCP config |
|
|
124
|
+
| MCP server dot stays red in popup | Make sure `gravity` is running (check IDE MCP status) |
|
|
125
|
+
| "Element not found: #selector" | Verify the selector exists on the current page |
|
|
126
|
+
| Chrome ≥ 116 required | The offscreen documents API was added in Chrome 116 |
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bridge.ts — WebSocket server that the Chrome extension connects to.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server IS the WebSocket server. The extension connects OUT to us.
|
|
5
|
+
* No native messaging. No manifest. No OS-specific registration.
|
|
6
|
+
*
|
|
7
|
+
* Extension ──WS──► Bridge (this) ──CDP──► MCP tools
|
|
8
|
+
*/
|
|
9
|
+
export declare function startBridge(): Promise<void>;
|
|
10
|
+
export declare function sendCDP(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
11
|
+
export declare function isExtensionConnected(): boolean;
|
|
12
|
+
export declare function getBridgePort(): number;
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bridge.ts — WebSocket server that the Chrome extension connects to.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server IS the WebSocket server. The extension connects OUT to us.
|
|
5
|
+
* No native messaging. No manifest. No OS-specific registration.
|
|
6
|
+
*
|
|
7
|
+
* Extension ──WS──► Bridge (this) ──CDP──► MCP tools
|
|
8
|
+
*/
|
|
9
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
10
|
+
const PORT = process.env.GRAVITY_PORT ? Number(process.env.GRAVITY_PORT) : 9224;
|
|
11
|
+
const CDP_TIMEOUT_MS = 10_000;
|
|
12
|
+
let extensionSocket = null;
|
|
13
|
+
let msgIdCounter = 1;
|
|
14
|
+
const pending = new Map();
|
|
15
|
+
// ── Start the bridge server ───────────────────────────────────────────────────
|
|
16
|
+
export function startBridge() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const wss = new WebSocketServer({ port: PORT, host: '127.0.0.1' });
|
|
19
|
+
wss.on('error', (err) => {
|
|
20
|
+
if (err.code === 'EADDRINUSE') {
|
|
21
|
+
reject(new Error(`Port ${PORT} is already in use. ` +
|
|
22
|
+
`Set GRAVITY_PORT env var to use a different port, ` +
|
|
23
|
+
`then reload the extension popup to reconnect.`));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
reject(err);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
wss.on('listening', () => {
|
|
30
|
+
console.error(`[gravity] bridge listening on ws://127.0.0.1:${PORT}`);
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
wss.on('connection', (ws, req) => {
|
|
34
|
+
const origin = req.headers.origin ?? 'unknown';
|
|
35
|
+
console.error(`[gravity] extension connected (origin: ${origin})`);
|
|
36
|
+
// Only one extension at a time — close the old one
|
|
37
|
+
if (extensionSocket && extensionSocket.readyState === WebSocket.OPEN) {
|
|
38
|
+
extensionSocket.close();
|
|
39
|
+
}
|
|
40
|
+
extensionSocket = ws;
|
|
41
|
+
ws.on('message', (data) => {
|
|
42
|
+
try {
|
|
43
|
+
const msg = JSON.parse(data.toString());
|
|
44
|
+
if (msg.type === 'cdp_response') {
|
|
45
|
+
const p = pending.get(msg.id);
|
|
46
|
+
if (p) {
|
|
47
|
+
clearTimeout(p.timer);
|
|
48
|
+
pending.delete(msg.id);
|
|
49
|
+
if (msg.error) {
|
|
50
|
+
p.reject(new Error(msg.error.message));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
p.resolve(msg.result);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
console.error('[gravity] bridge parse error:', e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
ws.on('close', () => {
|
|
63
|
+
console.error('[gravity] extension disconnected');
|
|
64
|
+
if (extensionSocket === ws)
|
|
65
|
+
extensionSocket = null;
|
|
66
|
+
// Reject all pending requests
|
|
67
|
+
for (const [id, p] of pending) {
|
|
68
|
+
clearTimeout(p.timer);
|
|
69
|
+
p.reject(new Error('Extension disconnected'));
|
|
70
|
+
pending.delete(id);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
ws.on('error', (err) => {
|
|
74
|
+
console.error('[gravity] extension socket error:', err.message);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ── Send a CDP command through the extension ──────────────────────────────────
|
|
80
|
+
export function sendCDP(method, params = {}) {
|
|
81
|
+
if (!extensionSocket || extensionSocket.readyState !== WebSocket.OPEN) {
|
|
82
|
+
return Promise.reject(new Error('Browser extension not connected. ' +
|
|
83
|
+
'Make sure Chrome is open, the Gravity extension is loaded, ' +
|
|
84
|
+
'and you clicked "Connect to Tab" in the popup.'));
|
|
85
|
+
}
|
|
86
|
+
const id = msgIdCounter++;
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
pending.delete(id);
|
|
90
|
+
reject(new Error(`CDP ${method} timed out after ${CDP_TIMEOUT_MS}ms`));
|
|
91
|
+
}, CDP_TIMEOUT_MS);
|
|
92
|
+
pending.set(id, { resolve, reject, timer });
|
|
93
|
+
extensionSocket.send(JSON.stringify({ type: 'cdp_request', id, method, params }));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// ── Status helpers ────────────────────────────────────────────────────────────
|
|
97
|
+
export function isExtensionConnected() {
|
|
98
|
+
return extensionSocket !== null && extensionSocket.readyState === WebSocket.OPEN;
|
|
99
|
+
}
|
|
100
|
+
export function getBridgePort() {
|
|
101
|
+
return PORT;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli.ts — Gravity v3 CLI entry point
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* gravity — start the MCP server (default)
|
|
7
|
+
* gravity doctor — check setup status
|
|
8
|
+
*/
|
|
9
|
+
import { program } from 'commander';
|
|
10
|
+
import { createRequire } from 'module';
|
|
11
|
+
import { dirname, resolve } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const pkg = require('../package.json');
|
|
17
|
+
program
|
|
18
|
+
.name('gravity')
|
|
19
|
+
.description('AI-powered CSS layout diagnostics — MCP server')
|
|
20
|
+
.version(pkg.version);
|
|
21
|
+
// ── doctor ────────────────────────────────────────────────────────────────────
|
|
22
|
+
program
|
|
23
|
+
.command('doctor')
|
|
24
|
+
.description('Check setup status and print instructions')
|
|
25
|
+
.action(async () => {
|
|
26
|
+
const { default: WebSocket } = await import('ws');
|
|
27
|
+
const port = process.env.GRAVITY_PORT ? Number(process.env.GRAVITY_PORT) : 9224;
|
|
28
|
+
console.log('\nGravity v' + pkg.version + ' — setup check');
|
|
29
|
+
console.log('─'.repeat(40));
|
|
30
|
+
// Node version
|
|
31
|
+
const nodeOk = parseInt(process.version.slice(1)) >= 16;
|
|
32
|
+
console.log(`${nodeOk ? '✓' : '✗'} Node.js ${process.version}${nodeOk ? '' : ' (need ≥16)'}`);
|
|
33
|
+
// Extension folder
|
|
34
|
+
const extDir = resolve(__dirname, '..', 'extension');
|
|
35
|
+
const extOk = existsSync(resolve(extDir, 'manifest.json'));
|
|
36
|
+
console.log(`${extOk ? '✓' : '✗'} Extension folder: ${extDir}`);
|
|
37
|
+
if (!extOk)
|
|
38
|
+
console.log(' ↳ Run: npm install -g gravity-lite');
|
|
39
|
+
// Port availability / existing server
|
|
40
|
+
try {
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
43
|
+
const t = setTimeout(() => { ws.close(); reject(new Error('timeout')); }, 2000);
|
|
44
|
+
ws.on('open', () => { clearTimeout(t); ws.close(); resolve(); });
|
|
45
|
+
ws.on('error', (e) => { clearTimeout(t); reject(e); });
|
|
46
|
+
});
|
|
47
|
+
console.log(`✓ MCP server is running on port ${port}`);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
console.log(`✗ MCP server not running on port ${port}`);
|
|
51
|
+
console.log(' ↳ Add gravity to your IDE MCP config and restart');
|
|
52
|
+
}
|
|
53
|
+
console.log('─'.repeat(40));
|
|
54
|
+
console.log('\nExtension setup (one-time):');
|
|
55
|
+
console.log(` 1. Open chrome://extensions → enable Developer Mode`);
|
|
56
|
+
console.log(` 2. "Load unpacked" → select: ${extDir}`);
|
|
57
|
+
console.log(` 3. Click the Gravity icon → "Connect to Tab"`);
|
|
58
|
+
console.log('\nMCP config (add to your IDE):');
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
mcpServers: {
|
|
61
|
+
gravity: { command: 'gravity' }
|
|
62
|
+
}
|
|
63
|
+
}, null, 2));
|
|
64
|
+
console.log();
|
|
65
|
+
});
|
|
66
|
+
// ── default: run MCP server ───────────────────────────────────────────────────
|
|
67
|
+
program.action(async () => {
|
|
68
|
+
const { GravityMCPServer } = await import('./mcp-server.js');
|
|
69
|
+
const srv = new GravityMCPServer();
|
|
70
|
+
await srv.run();
|
|
71
|
+
});
|
|
72
|
+
program.parse();
|
|
73
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diagnostics.ts — CSS layout & accessibility analysis helpers.
|
|
3
|
+
* Pure functions — no I/O, no side effects.
|
|
4
|
+
*/
|
|
5
|
+
export interface Bounds {
|
|
6
|
+
left: number;
|
|
7
|
+
top: number;
|
|
8
|
+
right: number;
|
|
9
|
+
bottom: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Issue {
|
|
14
|
+
type: string;
|
|
15
|
+
severity: 'high' | 'medium' | 'low';
|
|
16
|
+
message: string;
|
|
17
|
+
suggestion: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function extractBounds(model: {
|
|
20
|
+
content: number[];
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}): Bounds;
|
|
24
|
+
export declare function checkVisibility(styles: Map<string, string>): Issue[];
|
|
25
|
+
export declare function checkOffscreen(bounds: Bounds, vp: {
|
|
26
|
+
clientWidth: number;
|
|
27
|
+
clientHeight: number;
|
|
28
|
+
}): Issue[];
|
|
29
|
+
export declare function checkOverflow(styles: Map<string, string>): Issue[];
|
|
30
|
+
export declare function checkZIndex(styles: Map<string, string>): Issue[];
|
|
31
|
+
export declare function checkStackingContextCreators(styles: Map<string, string>): {
|
|
32
|
+
creates: boolean;
|
|
33
|
+
reasons: string[];
|
|
34
|
+
};
|
|
35
|
+
export declare function checkFlexGrid(styles: Map<string, string>): Issue[];
|
|
36
|
+
export declare function checkResponsive(styles: Map<string, string>, bounds: Bounds, vp: {
|
|
37
|
+
clientWidth: number;
|
|
38
|
+
}): Issue[];
|
|
39
|
+
export declare function checkAccessibilityStyles(styles: Map<string, string>): Issue[];
|
|
40
|
+
/**
|
|
41
|
+
* Parse any CSS color string to { r, g, b } (0–255).
|
|
42
|
+
* Supports: rgb(...), rgba(...), #rrggbb, #rgb
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseColor(color: string): {
|
|
45
|
+
r: number;
|
|
46
|
+
g: number;
|
|
47
|
+
b: number;
|
|
48
|
+
} | null;
|
|
49
|
+
/** Relative luminance per WCAG 2.1 */
|
|
50
|
+
export declare function relativeLuminance(r: number, g: number, b: number): number;
|
|
51
|
+
/** Contrast ratio between two colours (1–21) */
|
|
52
|
+
export declare function contrastRatio(c1: {
|
|
53
|
+
r: number;
|
|
54
|
+
g: number;
|
|
55
|
+
b: number;
|
|
56
|
+
}, c2: {
|
|
57
|
+
r: number;
|
|
58
|
+
g: number;
|
|
59
|
+
b: number;
|
|
60
|
+
}): number;
|
|
61
|
+
export declare function checkColorContrast(styles: Map<string, string>): Issue[];
|
|
62
|
+
export declare function checkCustomProperties(styles: Map<string, string>): Issue[];
|
|
63
|
+
export declare function sortBySeverity(issues: Issue[]): Issue[];
|
|
64
|
+
export declare function validateSelector(selector: string): {
|
|
65
|
+
valid: boolean;
|
|
66
|
+
error?: string;
|
|
67
|
+
};
|