@machfivetechchicago/machvive-webmcp-ai 5.6.1 → 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 +41 -24
- package/src/wc/machvive-webmcp-inspect/machvive-webmcp-inspect.d.ts +4 -0
- package/src/wc/machvive-webmcp-inspect/machvive-webmcp-inspect.js +52 -33
- 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,40 +369,45 @@ export function installAnalytics(log = callLog) {
|
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
const STYLES = `
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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); }
|
|
376
379
|
:host([hidden]) { display: none; }
|
|
377
380
|
.bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
|
|
378
381
|
.count { font-weight: 600; margin-right: auto; }
|
|
379
382
|
/* color is required, not decorative: form controls do not inherit it, so
|
|
380
383
|
without this the UA picks one per theme and white-on-white can result. */
|
|
381
|
-
button { font: inherit; color:
|
|
382
|
-
border
|
|
383
|
-
|
|
384
|
-
button
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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); }
|
|
388
392
|
li:last-child { border-bottom: 0; }
|
|
389
393
|
.row { display: flex; gap: 8px; align-items: center; padding: 6px 10px; cursor: pointer; }
|
|
390
|
-
.row:hover { background:
|
|
394
|
+
.row:hover { background: var(--mv-hover); }
|
|
391
395
|
.tool { font-family: ui-monospace, monospace; font-weight: 600; }
|
|
392
396
|
.status { font-size: 11px; padding: 1px 6px; border-radius: 10px; }
|
|
393
|
-
.status.ok { background:
|
|
394
|
-
.status.error { background:
|
|
395
|
-
.ms { color:
|
|
396
|
-
.detail { padding: 8px 10px; background:
|
|
397
|
-
|
|
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; }
|
|
398
403
|
textarea { width: 100%; box-sizing: border-box; font-family: ui-monospace, monospace;
|
|
399
|
-
font-size: 12px; color:
|
|
400
|
-
border-radius: 4px; padding: 5px; }
|
|
401
|
-
pre { margin: 0; padding: 6px; color:
|
|
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;
|
|
402
408
|
font-size: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
|
|
403
|
-
.empty { padding: 20px; text-align: center; color:
|
|
404
|
-
.err { color:
|
|
409
|
+
.empty { padding: 20px; text-align: center; color: var(--mv-faint); }
|
|
410
|
+
.err { color: var(--mv-err-fg); }
|
|
405
411
|
`;
|
|
406
412
|
|
|
407
413
|
export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
@@ -410,7 +416,7 @@ export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
|
410
416
|
#onChange = () => this.#render();
|
|
411
417
|
|
|
412
418
|
static get observedAttributes() {
|
|
413
|
-
return ['datalayer'];
|
|
419
|
+
return ['datalayer', 'theme'];
|
|
414
420
|
}
|
|
415
421
|
|
|
416
422
|
constructor() {
|
|
@@ -433,6 +439,17 @@ export class MachviveWebmcpAnalytics extends HTMLElement {
|
|
|
433
439
|
globalThis.window.removeEventListener(CALL_EVENT, this.#maybeAutoPush);
|
|
434
440
|
}
|
|
435
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
|
+
|
|
436
453
|
/** The shared call log, for page code that wants direct access. */
|
|
437
454
|
get log() {
|
|
438
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,71 +32,78 @@ function readControl(input, schema = {}) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const STYLES = `
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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); }
|
|
39
41
|
:host([hidden]) { display: none; }
|
|
40
42
|
|
|
41
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. */
|
|
42
46
|
:host([floating]) { position: fixed; right: 16px; bottom: 16px; z-index: 2147483000;
|
|
43
|
-
display: block; width: auto; }
|
|
47
|
+
display: block; width: auto; background: transparent; }
|
|
44
48
|
:host([floating]) .panel { display: none; width: min(420px, calc(100vw - 32px));
|
|
45
49
|
max-height: min(70vh, 560px); overflow: auto;
|
|
46
|
-
box-shadow: 0 8px 28px
|
|
50
|
+
box-shadow: 0 8px 28px var(--mv-shadow); background: var(--mv-bg); }
|
|
47
51
|
:host([floating][open]) .panel { display: block; }
|
|
48
52
|
:host([floating]) .fab { display: inline-flex; }
|
|
49
53
|
.fab { display: none; align-items: center; gap: 6px; margin-top: 8px; float: right;
|
|
50
|
-
padding: 7px 13px; border-radius: 999px; border: 1px solid
|
|
51
|
-
color:
|
|
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); }
|
|
52
57
|
|
|
53
|
-
.panel { border: 1px solid
|
|
58
|
+
.panel { border: 1px solid var(--mv-border); border-radius: 8px; overflow: hidden;
|
|
59
|
+
background: var(--mv-bg); }
|
|
54
60
|
header { display: flex; align-items: center; gap: 8px; padding: 7px 10px;
|
|
55
|
-
background:
|
|
61
|
+
background: var(--mv-surface); color: var(--mv-fg);
|
|
62
|
+
border-bottom: 1px solid var(--mv-border-soft); font-weight: 600; }
|
|
56
63
|
header .close { margin-left: auto; border: 0; background: none; cursor: pointer;
|
|
57
|
-
font-size: 16px; line-height: 1; color:
|
|
64
|
+
font-size: 16px; line-height: 1; color: var(--mv-muted); }
|
|
58
65
|
:host(:not([floating])) header .close { display: none; }
|
|
59
66
|
|
|
60
67
|
.body { display: flex; min-height: 150px; }
|
|
61
68
|
@media (max-width: 520px) { .body { flex-direction: column; } }
|
|
62
69
|
|
|
63
70
|
.tools { flex: 0 1 auto; min-width: 120px; max-width: 200px;
|
|
64
|
-
border-right: 1px solid
|
|
71
|
+
border-right: 1px solid var(--mv-border-soft); overflow-y: auto; }
|
|
65
72
|
@media (max-width: 520px) { .tools { flex: none; max-width: none; border-right: 0;
|
|
66
|
-
border-bottom: 1px solid
|
|
73
|
+
border-bottom: 1px solid var(--mv-border-soft); } }
|
|
67
74
|
/* Tool names are arbitrary identifiers; long ones must wrap inside the column
|
|
68
75
|
rather than spill over the divider. */
|
|
69
76
|
.tools button { display: block; width: 100%; text-align: left; padding: 6px 10px;
|
|
70
|
-
border: 0; background: none; color:
|
|
71
|
-
font-family: ui-monospace, monospace; font-size: 12px;
|
|
72
|
-
overflow-wrap: anywhere; border-bottom: 1px solid
|
|
73
|
-
.tools button:hover { background:
|
|
74
|
-
.tools button[aria-current="true"] { background:
|
|
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; }
|
|
75
82
|
|
|
76
83
|
.form { flex: 1; padding: 10px; min-width: 0; }
|
|
77
|
-
.desc { color:
|
|
84
|
+
.desc { color: var(--mv-muted); margin: 0 0 8px; }
|
|
78
85
|
label { display: block; margin-bottom: 7px; }
|
|
79
86
|
.name { font-family: ui-monospace, monospace; font-size: 12px; }
|
|
80
|
-
.req { color:
|
|
81
|
-
.hint { color:
|
|
87
|
+
.req { color: var(--mv-err-fg); }
|
|
88
|
+
.hint { color: var(--mv-faint); font-size: 11px; }
|
|
82
89
|
/* color/background are required, not decorative: form controls do not inherit
|
|
83
90
|
them, so without these the UA picks per-theme defaults and text can render
|
|
84
91
|
white on white. */
|
|
85
92
|
input, select, textarea { width: 100%; box-sizing: border-box; font: inherit; font-size: 12px;
|
|
86
|
-
color:
|
|
87
|
-
border: 1px solid
|
|
93
|
+
color: var(--mv-fg); background: var(--mv-input-bg); padding: 4px 6px;
|
|
94
|
+
border: 1px solid var(--mv-control-border); border-radius: 4px; }
|
|
88
95
|
input[type="checkbox"] { width: auto; }
|
|
89
96
|
textarea { font-family: ui-monospace, monospace; }
|
|
90
|
-
.run { margin-top: 4px; padding: 5px 14px; border: 1px solid
|
|
91
|
-
|
|
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; }
|
|
92
100
|
.run:disabled { opacity: .6; cursor: default; }
|
|
93
|
-
pre { margin: 8px 0 0; padding: 7px; color:
|
|
94
|
-
border
|
|
95
|
-
max-height: 180px; overflow: auto; }
|
|
96
|
-
pre.error { background:
|
|
97
|
-
.field-error { color:
|
|
98
|
-
.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); }
|
|
99
107
|
`;
|
|
100
108
|
|
|
101
109
|
export class MachviveWebmcpInspect extends HTMLElement {
|
|
@@ -104,7 +112,7 @@ export class MachviveWebmcpInspect extends HTMLElement {
|
|
|
104
112
|
#onToolsChanged = () => this.#render();
|
|
105
113
|
|
|
106
114
|
static get observedAttributes() {
|
|
107
|
-
return ['floating', 'open'];
|
|
115
|
+
return ['floating', 'open', 'theme'];
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
constructor() {
|
|
@@ -126,6 +134,17 @@ export class MachviveWebmcpInspect extends HTMLElement {
|
|
|
126
134
|
if (this.shadowRoot?.getElementById('root')) this.#render();
|
|
127
135
|
}
|
|
128
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
|
+
|
|
129
148
|
/** Opens the panel (floating mode only). */
|
|
130
149
|
show() {
|
|
131
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
|
+
`;
|