@machfivetechchicago/machvive-webmcp-ai 5.6.0 → 5.7.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 +48 -0
- package/package.json +1 -1
- package/src/wc/machvive-lorum-ipsum/machvive-lorum-ipsum.d.ts +4 -1
- package/src/wc/machvive-lorum-ipsum/machvive-lorum-ipsum.js +16 -0
- package/src/wc/machvive-webmcp-analytics/machvive-webmcp-analytics.d.ts +2 -0
- package/src/wc/machvive-webmcp-analytics/machvive-webmcp-analytics.js +43 -19
- package/src/wc/machvive-webmcp-inspect/machvive-webmcp-inspect.d.ts +4 -0
- package/src/wc/machvive-webmcp-inspect/machvive-webmcp-inspect.js +59 -27
- package/src/wc/shared/theme.js +86 -0
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ elements with Shadow DOM that work anywhere `customElements` does.
|
|
|
29
29
|
| WebMCP analytics | `<machvive-webmcp-analytics>` | Captures every tool call for listing, editing, export, replay, and dataLayer |
|
|
30
30
|
| Lorum Ipsum | `<machvive-lorum-ipsum>` | Placeholder copy that projects slotted content |
|
|
31
31
|
|
|
32
|
+
<img width="1098" height="894" alt="m5t_machvive_ai_webmcp" src="https://github.com/user-attachments/assets/a1433ff0-f8a9-46fb-ad59-735aed155ff8" />
|
|
33
|
+
|
|
32
34
|
## ⚡ Integration with Vite (Vanilla JS)
|
|
33
35
|
|
|
34
36
|
This package is optimized for modern ESM environments like Vite. You can import individual components or the entire library at once.
|
|
@@ -206,6 +208,52 @@ Each call then pushes `{ event: 'webmcp_tool_call', webmcp_tool, webmcp_status,
|
|
|
206
208
|
webmcp_duration_ms, webmcp_params }`. Without the attribute, push individual entries
|
|
207
209
|
on demand with `callLog.pushToDataLayer(id)` or the per-entry button in the UI.
|
|
208
210
|
|
|
211
|
+
## 🌓 Theming
|
|
212
|
+
|
|
213
|
+
The UI components follow the viewer's OS preference automatically — no
|
|
214
|
+
configuration, no flash of the wrong palette. Set `theme` to override:
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<machvive-webmcp-inspect></machvive-webmcp-inspect> <!-- follows the OS -->
|
|
218
|
+
<machvive-webmcp-analytics theme="dark"></machvive-webmcp-analytics>
|
|
219
|
+
<machvive-webmcp-inspect theme="light"></machvive-webmcp-inspect>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`theme` is also a reflected property, so `el.theme = 'dark'` and
|
|
223
|
+
`el.theme = null` (back to following the OS) work from script.
|
|
224
|
+
|
|
225
|
+
### Overriding the palette
|
|
226
|
+
|
|
227
|
+
Colors are CSS custom properties on the host. Custom properties inherit *through*
|
|
228
|
+
shadow boundaries where ordinary styles do not, so you can restyle the components
|
|
229
|
+
from your own stylesheet without `::part` or `!important`:
|
|
230
|
+
|
|
231
|
+
```css
|
|
232
|
+
machvive-webmcp-inspect,
|
|
233
|
+
machvive-webmcp-analytics {
|
|
234
|
+
--mv-accent: #7c3aed;
|
|
235
|
+
--mv-bg: #ffffff;
|
|
236
|
+
--mv-fg: #111827;
|
|
237
|
+
--mv-border: #e5e7eb;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Every token has both a light and a dark value; overriding one replaces it in both
|
|
242
|
+
themes unless you scope your override inside your own media query.
|
|
243
|
+
|
|
244
|
+
| Token | Role |
|
|
245
|
+
| --- | --- |
|
|
246
|
+
| `--mv-fg` / `--mv-muted` / `--mv-faint` | Text: primary, secondary, hints |
|
|
247
|
+
| `--mv-bg` / `--mv-surface` / `--mv-input-bg` | Panel, raised areas, form fields |
|
|
248
|
+
| `--mv-border` / `--mv-border-soft` / `--mv-control-border` | Outlines, dividers, controls |
|
|
249
|
+
| `--mv-hover` / `--mv-selected` | Interactive states |
|
|
250
|
+
| `--mv-accent` / `--mv-accent-fg` | Primary button |
|
|
251
|
+
| `--mv-ok-bg` / `--mv-ok-fg` / `--mv-err-bg` / `--mv-err-fg` | Status badges |
|
|
252
|
+
| `--mv-danger` / `--mv-danger-border` | Destructive actions |
|
|
253
|
+
|
|
254
|
+
Every combination of OS preference and `theme` is verified to meet WCAG AA
|
|
255
|
+
(≥ 4.5:1) across the full UI. If you override tokens, re-check your own contrast.
|
|
256
|
+
|
|
209
257
|
## TypeScript
|
|
210
258
|
|
|
211
259
|
Declarations ship with the package — no `@types/*` needed. Importing a component
|
package/package.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Placeholder-copy element. Projects light DOM through a slot. */
|
|
2
|
-
export class MachviveLorumIpsum extends HTMLElement {
|
|
2
|
+
export class MachviveLorumIpsum extends HTMLElement {
|
|
3
|
+
/** Forces a palette regardless of the OS preference. null follows the OS. */
|
|
4
|
+
theme: 'light' | 'dark' | null;
|
|
5
|
+
}
|
|
3
6
|
|
|
4
7
|
declare global {
|
|
5
8
|
interface HTMLElementTagNameMap {
|
|
@@ -4,10 +4,26 @@ export class MachviveLorumIpsum extends HTMLElement {
|
|
|
4
4
|
this.attachShadow({ mode: 'open' });
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
/** Forces a palette regardless of the OS preference. null follows the OS. */
|
|
9
|
+
get theme() {
|
|
10
|
+
return this.getAttribute('theme');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
set theme(value) {
|
|
14
|
+
if (value == null) this.removeAttribute('theme');
|
|
15
|
+
else this.setAttribute('theme', value);
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
connectedCallback() {
|
|
8
19
|
this.shadowRoot.innerHTML = `
|
|
9
20
|
<style>
|
|
10
21
|
:host { display: block; font-family: sans-serif; color: #333; }
|
|
22
|
+
@media (prefers-color-scheme: dark) {
|
|
23
|
+
:host(:not([theme="light"])) { color: #e8eaed; }
|
|
24
|
+
}
|
|
25
|
+
:host([theme="dark"]) { color: #e8eaed; }
|
|
26
|
+
:host([theme="light"]) { color: #333; }
|
|
11
27
|
</style>
|
|
12
28
|
<div>
|
|
13
29
|
<slot>Lorem ipsum dolor sit amet...</slot>
|
|
@@ -68,6 +68,8 @@ export function installAnalytics(log?: CallLog): boolean;
|
|
|
68
68
|
|
|
69
69
|
export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
70
70
|
readonly log: CallLog;
|
|
71
|
+
/** Forces a palette regardless of the OS preference. null follows the OS. */
|
|
72
|
+
theme: 'light' | 'dark' | null;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
declare global {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// exactly what capturing every call requires — finds no registry to instrument
|
|
14
14
|
// and silently captures nothing.
|
|
15
15
|
import '../machvive-webmcp-polyfill/machvive-webmcp-polyfill.js';
|
|
16
|
+
import { THEME_CSS } from '../shared/theme.js';
|
|
16
17
|
|
|
17
18
|
export const CALL_EVENT = 'machvive-webmcp-call';
|
|
18
19
|
export const DB_NAME = 'machvive-webmcp';
|
|
@@ -368,33 +369,45 @@ export function installAnalytics(log = callLog) {
|
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
const STYLES = `
|
|
371
|
-
|
|
372
|
+
${THEME_CSS}
|
|
373
|
+
|
|
374
|
+
/* Painting the background is not optional: this component sets its own text
|
|
375
|
+
colour per theme, so it cannot rely on inheriting a compatible surface from
|
|
376
|
+
whatever page embeds it. */
|
|
377
|
+
:host { display: block; font: 13px/1.5 system-ui, sans-serif;
|
|
378
|
+
color: var(--mv-fg); background: var(--mv-bg); }
|
|
372
379
|
:host([hidden]) { display: none; }
|
|
373
380
|
.bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
|
|
374
381
|
.count { font-weight: 600; margin-right: auto; }
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
button
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
+
/* color is required, not decorative: form controls do not inherit it, so
|
|
383
|
+
without this the UA picks one per theme and white-on-white can result. */
|
|
384
|
+
button { font: inherit; color: var(--mv-fg); padding: 3px 9px;
|
|
385
|
+
border: 1px solid var(--mv-control-border); border-radius: 4px;
|
|
386
|
+
background: var(--mv-bg); cursor: pointer; }
|
|
387
|
+
button:hover { background: var(--mv-hover); }
|
|
388
|
+
button.danger { color: var(--mv-danger); border-color: var(--mv-danger-border); }
|
|
389
|
+
ol { list-style: none; margin: 0; padding: 0; border: 1px solid var(--mv-border);
|
|
390
|
+
border-radius: 6px; max-height: 380px; overflow-y: auto; background: var(--mv-bg); }
|
|
391
|
+
li { border-bottom: 1px solid var(--mv-border-soft); }
|
|
382
392
|
li:last-child { border-bottom: 0; }
|
|
383
393
|
.row { display: flex; gap: 8px; align-items: center; padding: 6px 10px; cursor: pointer; }
|
|
384
|
-
.row:hover { background:
|
|
394
|
+
.row:hover { background: var(--mv-hover); }
|
|
385
395
|
.tool { font-family: ui-monospace, monospace; font-weight: 600; }
|
|
386
396
|
.status { font-size: 11px; padding: 1px 6px; border-radius: 10px; }
|
|
387
|
-
.status.ok { background:
|
|
388
|
-
.status.error { background:
|
|
389
|
-
.ms { color:
|
|
390
|
-
.detail { padding: 8px 10px; background:
|
|
391
|
-
|
|
397
|
+
.status.ok { background: var(--mv-ok-bg); color: var(--mv-ok-fg); }
|
|
398
|
+
.status.error { background: var(--mv-err-bg); color: var(--mv-err-fg); }
|
|
399
|
+
.ms { color: var(--mv-muted); font-size: 11px; margin-left: auto; }
|
|
400
|
+
.detail { padding: 8px 10px; background: var(--mv-surface);
|
|
401
|
+
border-top: 1px solid var(--mv-border-soft); }
|
|
402
|
+
.detail label { display: block; font-size: 11px; color: var(--mv-muted); margin: 6px 0 2px; }
|
|
392
403
|
textarea { width: 100%; box-sizing: border-box; font-family: ui-monospace, monospace;
|
|
393
|
-
font-size: 12px;
|
|
394
|
-
|
|
404
|
+
font-size: 12px; color: var(--mv-fg); background: var(--mv-input-bg);
|
|
405
|
+
border: 1px solid var(--mv-control-border); border-radius: 4px; padding: 5px; }
|
|
406
|
+
pre { margin: 0; padding: 6px; color: var(--mv-fg); background: var(--mv-bg);
|
|
407
|
+
border: 1px solid var(--mv-border-soft); border-radius: 4px;
|
|
395
408
|
font-size: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
|
|
396
|
-
.empty { padding: 20px; text-align: center; color:
|
|
397
|
-
.err { color:
|
|
409
|
+
.empty { padding: 20px; text-align: center; color: var(--mv-faint); }
|
|
410
|
+
.err { color: var(--mv-err-fg); }
|
|
398
411
|
`;
|
|
399
412
|
|
|
400
413
|
export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
@@ -403,7 +416,7 @@ export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
|
403
416
|
#onChange = () => this.#render();
|
|
404
417
|
|
|
405
418
|
static get observedAttributes() {
|
|
406
|
-
return ['datalayer'];
|
|
419
|
+
return ['datalayer', 'theme'];
|
|
407
420
|
}
|
|
408
421
|
|
|
409
422
|
constructor() {
|
|
@@ -426,6 +439,17 @@ export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
|
426
439
|
globalThis.window.removeEventListener(CALL_EVENT, this.#maybeAutoPush);
|
|
427
440
|
}
|
|
428
441
|
|
|
442
|
+
|
|
443
|
+
/** Forces a palette regardless of the OS preference. null follows the OS. */
|
|
444
|
+
get theme() {
|
|
445
|
+
return this.getAttribute('theme');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
set theme(value) {
|
|
449
|
+
if (value == null) this.removeAttribute('theme');
|
|
450
|
+
else this.setAttribute('theme', value);
|
|
451
|
+
}
|
|
452
|
+
|
|
429
453
|
/** The shared call log, for page code that wants direct access. */
|
|
430
454
|
get log() {
|
|
431
455
|
return this.#log;
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Lists registered WebMCP tools, builds a form from each tool's inputSchema,
|
|
3
3
|
* and executes it. Inline by default; `floating` docks it as an overlay.
|
|
4
4
|
*/
|
|
5
|
+
export type MachviveTheme = 'light' | 'dark' | null;
|
|
6
|
+
|
|
5
7
|
export class MachviveWebmcpInspect extends HTMLElement {
|
|
8
|
+
/** Forces a palette regardless of the OS preference. null follows the OS. */
|
|
9
|
+
theme: MachviveTheme;
|
|
6
10
|
/** Opens the panel (floating mode only). */
|
|
7
11
|
show(): void;
|
|
8
12
|
/** Closes the panel (floating mode only). */
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* invocation (`callTool`) are additions the bare spec does not provide.
|
|
10
10
|
*/
|
|
11
11
|
import { TOOLS_CHANGED_EVENT } from '../machvive-webmcp-polyfill/machvive-webmcp-polyfill.js';
|
|
12
|
+
import { THEME_CSS } from '../shared/theme.js';
|
|
12
13
|
|
|
13
14
|
/** Reads a form control back as the JSON type its schema calls for. */
|
|
14
15
|
function readControl(input, schema = {}) {
|
|
@@ -31,58 +32,78 @@ function readControl(input, schema = {}) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const STYLES = `
|
|
34
|
-
|
|
35
|
+
${THEME_CSS}
|
|
36
|
+
|
|
37
|
+
/* See analytics: a component that themes its own text must paint its own
|
|
38
|
+
surface rather than assume the embedding page supplies a matching one. */
|
|
39
|
+
:host { display: block; font: 13px/1.5 system-ui, sans-serif;
|
|
40
|
+
color: var(--mv-fg); background: var(--mv-bg); }
|
|
35
41
|
:host([hidden]) { display: none; }
|
|
36
42
|
|
|
37
43
|
/* Floating mode docks the panel without disturbing page layout. */
|
|
44
|
+
/* Floating mode is a detached panel: the .panel and .fab paint themselves, so
|
|
45
|
+
the host must stay transparent or it draws a block over the page. */
|
|
38
46
|
:host([floating]) { position: fixed; right: 16px; bottom: 16px; z-index: 2147483000;
|
|
39
|
-
display: block; width: auto; }
|
|
47
|
+
display: block; width: auto; background: transparent; }
|
|
40
48
|
:host([floating]) .panel { display: none; width: min(420px, calc(100vw - 32px));
|
|
41
49
|
max-height: min(70vh, 560px); overflow: auto;
|
|
42
|
-
box-shadow: 0 8px 28px
|
|
50
|
+
box-shadow: 0 8px 28px var(--mv-shadow); background: var(--mv-bg); }
|
|
43
51
|
:host([floating][open]) .panel { display: block; }
|
|
44
52
|
:host([floating]) .fab { display: inline-flex; }
|
|
45
53
|
.fab { display: none; align-items: center; gap: 6px; margin-top: 8px; float: right;
|
|
46
|
-
padding: 7px 13px; border-radius: 999px; border: 1px solid
|
|
47
|
-
|
|
54
|
+
padding: 7px 13px; border-radius: 999px; border: 1px solid var(--mv-control-border);
|
|
55
|
+
background: var(--mv-bg); color: var(--mv-fg); cursor: pointer; font: inherit;
|
|
56
|
+
box-shadow: 0 2px 8px var(--mv-shadow); }
|
|
48
57
|
|
|
49
|
-
.panel { border: 1px solid
|
|
58
|
+
.panel { border: 1px solid var(--mv-border); border-radius: 8px; overflow: hidden;
|
|
59
|
+
background: var(--mv-bg); }
|
|
50
60
|
header { display: flex; align-items: center; gap: 8px; padding: 7px 10px;
|
|
51
|
-
background:
|
|
61
|
+
background: var(--mv-surface); color: var(--mv-fg);
|
|
62
|
+
border-bottom: 1px solid var(--mv-border-soft); font-weight: 600; }
|
|
52
63
|
header .close { margin-left: auto; border: 0; background: none; cursor: pointer;
|
|
53
|
-
font-size: 16px; line-height: 1; color:
|
|
64
|
+
font-size: 16px; line-height: 1; color: var(--mv-muted); }
|
|
54
65
|
:host(:not([floating])) header .close { display: none; }
|
|
55
66
|
|
|
56
67
|
.body { display: flex; min-height: 150px; }
|
|
57
68
|
@media (max-width: 520px) { .body { flex-direction: column; } }
|
|
58
69
|
|
|
59
|
-
.tools { flex: 0
|
|
60
|
-
|
|
70
|
+
.tools { flex: 0 1 auto; min-width: 120px; max-width: 200px;
|
|
71
|
+
border-right: 1px solid var(--mv-border-soft); overflow-y: auto; }
|
|
72
|
+
@media (max-width: 520px) { .tools { flex: none; max-width: none; border-right: 0;
|
|
73
|
+
border-bottom: 1px solid var(--mv-border-soft); } }
|
|
74
|
+
/* Tool names are arbitrary identifiers; long ones must wrap inside the column
|
|
75
|
+
rather than spill over the divider. */
|
|
61
76
|
.tools button { display: block; width: 100%; text-align: left; padding: 6px 10px;
|
|
62
|
-
border: 0; background: none;
|
|
63
|
-
font-family: ui-monospace, monospace;
|
|
64
|
-
|
|
65
|
-
.tools button
|
|
77
|
+
border: 0; background: none; color: var(--mv-fg); cursor: pointer;
|
|
78
|
+
font: inherit; font-family: ui-monospace, monospace; font-size: 12px;
|
|
79
|
+
overflow-wrap: anywhere; border-bottom: 1px solid var(--mv-border-soft); }
|
|
80
|
+
.tools button:hover { background: var(--mv-hover); }
|
|
81
|
+
.tools button[aria-current="true"] { background: var(--mv-selected); font-weight: 600; }
|
|
66
82
|
|
|
67
83
|
.form { flex: 1; padding: 10px; min-width: 0; }
|
|
68
|
-
.desc { color:
|
|
84
|
+
.desc { color: var(--mv-muted); margin: 0 0 8px; }
|
|
69
85
|
label { display: block; margin-bottom: 7px; }
|
|
70
86
|
.name { font-family: ui-monospace, monospace; font-size: 12px; }
|
|
71
|
-
.req { color:
|
|
72
|
-
.hint { color:
|
|
87
|
+
.req { color: var(--mv-err-fg); }
|
|
88
|
+
.hint { color: var(--mv-faint); font-size: 11px; }
|
|
89
|
+
/* color/background are required, not decorative: form controls do not inherit
|
|
90
|
+
them, so without these the UA picks per-theme defaults and text can render
|
|
91
|
+
white on white. */
|
|
73
92
|
input, select, textarea { width: 100%; box-sizing: border-box; font: inherit; font-size: 12px;
|
|
74
|
-
|
|
93
|
+
color: var(--mv-fg); background: var(--mv-input-bg); padding: 4px 6px;
|
|
94
|
+
border: 1px solid var(--mv-control-border); border-radius: 4px; }
|
|
75
95
|
input[type="checkbox"] { width: auto; }
|
|
76
96
|
textarea { font-family: ui-monospace, monospace; }
|
|
77
|
-
.run { margin-top: 4px; padding: 5px 14px; border: 1px solid
|
|
78
|
-
|
|
97
|
+
.run { margin-top: 4px; padding: 5px 14px; border: 1px solid var(--mv-accent);
|
|
98
|
+
border-radius: 4px; background: var(--mv-accent); color: var(--mv-accent-fg);
|
|
99
|
+
cursor: pointer; font: inherit; }
|
|
79
100
|
.run:disabled { opacity: .6; cursor: default; }
|
|
80
|
-
pre { margin: 8px 0 0; padding: 7px;
|
|
81
|
-
border
|
|
82
|
-
max-height: 180px; overflow: auto; }
|
|
83
|
-
pre.error { background:
|
|
84
|
-
.field-error { color:
|
|
85
|
-
.empty { padding: 20px; text-align: center; color:
|
|
101
|
+
pre { margin: 8px 0 0; padding: 7px; color: var(--mv-fg); background: var(--mv-surface);
|
|
102
|
+
border: 1px solid var(--mv-border-soft); border-radius: 4px; font-size: 12px;
|
|
103
|
+
white-space: pre-wrap; word-break: break-word; max-height: 180px; overflow: auto; }
|
|
104
|
+
pre.error { background: var(--mv-err-bg); border-color: var(--mv-err-border); color: var(--mv-err-fg); }
|
|
105
|
+
.field-error { color: var(--mv-err-fg); font-size: 11px; }
|
|
106
|
+
.empty { padding: 20px; text-align: center; color: var(--mv-faint); }
|
|
86
107
|
`;
|
|
87
108
|
|
|
88
109
|
export class MachviveWebmcpInspect extends HTMLElement {
|
|
@@ -91,7 +112,7 @@ export class MachviveWebmcpInspect extends HTMLElement {
|
|
|
91
112
|
#onToolsChanged = () => this.#render();
|
|
92
113
|
|
|
93
114
|
static get observedAttributes() {
|
|
94
|
-
return ['floating', 'open'];
|
|
115
|
+
return ['floating', 'open', 'theme'];
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
constructor() {
|
|
@@ -113,6 +134,17 @@ export class MachviveWebmcpInspect extends HTMLElement {
|
|
|
113
134
|
if (this.shadowRoot?.getElementById('root')) this.#render();
|
|
114
135
|
}
|
|
115
136
|
|
|
137
|
+
|
|
138
|
+
/** Forces a palette regardless of the OS preference. null follows the OS. */
|
|
139
|
+
get theme() {
|
|
140
|
+
return this.getAttribute('theme');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
set theme(value) {
|
|
144
|
+
if (value == null) this.removeAttribute('theme');
|
|
145
|
+
else this.setAttribute('theme', value);
|
|
146
|
+
}
|
|
147
|
+
|
|
116
148
|
/** Opens the panel (floating mode only). */
|
|
117
149
|
show() {
|
|
118
150
|
this.setAttribute('open', '');
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared design tokens for the WebMCP UI components.
|
|
3
|
+
*
|
|
4
|
+
* Tokens are plain custom properties on :host, so a consumer can override any of
|
|
5
|
+
* them from outside the shadow DOM — custom properties inherit through shadow
|
|
6
|
+
* boundaries where ordinary styles do not.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order, matching how a component should behave:
|
|
9
|
+
* 1. light palette on :host (the default)
|
|
10
|
+
* 2. dark palette when the OS asks for dark (unless theme="light" opts out)
|
|
11
|
+
* 3. dark palette when theme="dark" (explicit, wins over the OS)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const LIGHT = `
|
|
15
|
+
--mv-fg: #1a1a1a;
|
|
16
|
+
--mv-muted: #666;
|
|
17
|
+
--mv-faint: #6e6e6e;
|
|
18
|
+
--mv-bg: #fff;
|
|
19
|
+
--mv-surface: #fafafa;
|
|
20
|
+
--mv-input-bg: #fff;
|
|
21
|
+
--mv-border: #e2e2e2;
|
|
22
|
+
--mv-border-soft: #eee;
|
|
23
|
+
--mv-control-border: #ccc;
|
|
24
|
+
--mv-hover: #f2f2f2;
|
|
25
|
+
--mv-selected: #e8f0fe;
|
|
26
|
+
--mv-accent: #1565c0;
|
|
27
|
+
--mv-accent-fg: #fff;
|
|
28
|
+
--mv-ok-bg: #e6f4ea;
|
|
29
|
+
--mv-ok-fg: #0f6b2e;
|
|
30
|
+
--mv-err-bg: #fce8e6;
|
|
31
|
+
--mv-err-fg: #b3261e;
|
|
32
|
+
--mv-err-border: #f5c6c2;
|
|
33
|
+
--mv-danger: #a4161a;
|
|
34
|
+
--mv-danger-border: #d8a0a0;
|
|
35
|
+
--mv-shadow: rgba(0, 0, 0, .18);
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const DARK = `
|
|
39
|
+
--mv-fg: #e8eaed;
|
|
40
|
+
--mv-muted: #9aa0a6;
|
|
41
|
+
--mv-faint: #8b9096;
|
|
42
|
+
--mv-bg: #1f2125;
|
|
43
|
+
--mv-surface: #282b30;
|
|
44
|
+
--mv-input-bg: #16181b;
|
|
45
|
+
--mv-border: #3c4046;
|
|
46
|
+
--mv-border-soft: #33363b;
|
|
47
|
+
--mv-control-border: #4a4f56;
|
|
48
|
+
--mv-hover: #32363c;
|
|
49
|
+
--mv-selected: #1e3a5f;
|
|
50
|
+
--mv-accent: #5b9bf8;
|
|
51
|
+
--mv-accent-fg: #0b1220;
|
|
52
|
+
--mv-ok-bg: #16351f;
|
|
53
|
+
--mv-ok-fg: #7ee2a0;
|
|
54
|
+
--mv-err-bg: #3f1d1c;
|
|
55
|
+
--mv-err-fg: #ff9d97;
|
|
56
|
+
--mv-err-border: #5c2c2a;
|
|
57
|
+
--mv-danger: #ff9d97;
|
|
58
|
+
--mv-danger-border: #5c2c2a;
|
|
59
|
+
--mv-shadow: rgba(0, 0, 0, .5);
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* CSS declaring both palettes. Prepend to a component's stylesheet.
|
|
64
|
+
*
|
|
65
|
+
* `color-scheme: light dark` lets UA-rendered parts (scrollbars, select popups,
|
|
66
|
+
* checkboxes, focus rings) follow the same theme as the tokens — without it the
|
|
67
|
+
* browser would paint light controls onto a dark panel.
|
|
68
|
+
*/
|
|
69
|
+
export const THEME_CSS = `
|
|
70
|
+
:host {
|
|
71
|
+
color-scheme: light dark;
|
|
72
|
+
${LIGHT} }
|
|
73
|
+
|
|
74
|
+
@media (prefers-color-scheme: dark) {
|
|
75
|
+
:host(:not([theme="light"])) {
|
|
76
|
+
${DARK} }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Explicit choice beats the OS preference, in both directions. */
|
|
80
|
+
:host([theme="dark"]) {
|
|
81
|
+
${DARK} }
|
|
82
|
+
|
|
83
|
+
:host([theme="light"]) {
|
|
84
|
+
color-scheme: light;
|
|
85
|
+
${LIGHT} }
|
|
86
|
+
`;
|