@webmcpui/core 0.1.2 → 0.2.1

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 CHANGED
@@ -1,11 +1,19 @@
1
1
  # @webmcpui/core
2
2
 
3
- Framework-agnostic, WebMCP-native custom elements. Phase 1 ships form
4
- primitives shared behavior (form association, validation, WebMCP exposure,
5
- theming) lives in a `WmcpFormControl` base class; each element is a thin
6
- subclass that supplies its control and specifics.
3
+ Framework-agnostic, WebMCP-native custom elements. Every `<wmcp-*>` element is a
4
+ proper, accessible HTML control first and, when you opt in, also registers an
5
+ imperative [WebMCP](https://webmcpui.com/docs/webmcp) tool an agent can call.
7
6
 
8
- Shipped so far: `<wmcp-input>`, `<wmcp-textarea>`, `<wmcp-select>`, `<wmcp-checkbox>`.
7
+ **Two families of primitives:**
8
+
9
+ - **Form controls** expose a _value_ an agent can set — `<wmcp-input>`,
10
+ `<wmcp-textarea>`, `<wmcp-select>`, `<wmcp-checkbox>`, `<wmcp-radio>` /
11
+ `<wmcp-radio-group>`. Shared behavior (form association via `ElementInternals`,
12
+ Standard Schema validation, a11y, theming) lives in a `WmcpFormControl` base.
13
+ - **Interaction primitives** expose an _action_ an agent can trigger —
14
+ `<wmcp-button>`, `<wmcp-dialog>`, `<wmcp-menu>`, `<wmcp-tabs>`,
15
+ `<wmcp-popover>` — or, for `<wmcp-toast>`, a _reading_ an agent can perceive.
16
+ They share a `WmcpAction` / `WmcpExposable` base.
9
17
 
10
18
  One source of truth (vanilla custom elements built with [Lit](https://lit.dev)),
11
19
  two distribution channels: an ESM package for build tools, and a single-file
@@ -21,11 +29,12 @@ pnpm add @webmcpui/core @webmcpui/tokens
21
29
  import { defineComponents } from '@webmcpui/core';
22
30
  import '@webmcpui/tokens/css'; // the theme tokens (CSS custom properties)
23
31
 
24
- defineComponents(); // registers <wmcp-input> (and future <wmcp-*> elements)
32
+ defineComponents(); // registers every <wmcp-*> element
25
33
  ```
26
34
 
27
35
  ```html
28
36
  <wmcp-input label="Email" name="email" type="email"></wmcp-input>
37
+ <wmcp-button variant="primary">Save</wmcp-button>
29
38
  ```
30
39
 
31
40
  Importing the package does **not** register elements — you call
@@ -45,7 +54,7 @@ For Webflow / WordPress / hand-written HTML — one tag, elements auto-register:
45
54
 
46
55
  See `examples/plain-html.html` for a working local version.
47
56
 
48
- ## Standard Schema validation
57
+ ## Standard Schema validation (form controls)
49
58
 
50
59
  Bring any [Standard Schema](https://standardschema.dev) validator — Zod,
51
60
  Valibot, ArkType — set it as the `schema` property. No bespoke schema language.
@@ -59,51 +68,64 @@ input.schema = z.string().email('Enter a valid email');
59
68
 
60
69
  Validation runs on input and during native form validation; failures set
61
70
  `aria-invalid`, render an error message in a live region, and propagate to the
62
- containing `<form>` via ElementInternals.
63
-
64
- > **Note:** Standard Schema validates values but does not emit JSON Schema, so
65
- > the WebMCP tool's parameter schema is derived from the input `type`, not from
66
- > the validator. Richer tool schemas are a future enhancement.
71
+ containing `<form>` via `ElementInternals`.
67
72
 
68
73
  ## WebMCP exposure
69
74
 
70
- Opt in with `expose`. The element registers an imperative WebMCP tool
71
- (`navigator.modelContext.registerTool`) on connect and unregisters on
72
- disconnect. It is feature-detected a complete no-op when no agent/host is
73
- present (the common case today), so the input is always a good form control
74
- first.
75
+ Opt in with `expose`. The element registers an imperative WebMCP tool on connect
76
+ and unregisters on disconnect. It is feature-detected — preferring
77
+ `document.modelContext` (canonical as of the Chrome 149+ origin trial) and
78
+ falling back to the deprecated `navigator.modelContext` and a complete no-op
79
+ when no host is present, so the element is always a good control first.
75
80
 
76
81
  ```html
82
+ <!-- form control → a "fill" tool that sets a value -->
77
83
  <wmcp-input label="Email" name="email" expose></wmcp-input>
78
- <!-- registers a "fill_email" tool an agent can call -->
84
+
85
+ <!-- interaction primitive → an "action" tool the agent can trigger -->
86
+ <wmcp-button tool-name="book_appointment" expose>Book</wmcp-button>
87
+
88
+ <!-- a menu → a parameterized action (the agent picks which item) -->
89
+ <wmcp-menu name="row_action" label="Actions" expose>
90
+ <option value="edit">Edit</option>
91
+ <option value="delete">Delete</option>
92
+ </wmcp-menu>
79
93
  ```
80
94
 
95
+ Consequential steps stay a deliberate human action: an agent can _set_ a value
96
+ or _open_ a dialog, but submitting/confirming is the person's to make.
97
+
81
98
  ## Testing without a real agent
82
99
 
83
- No mainstream agent calls WebMCP yet, so `@webmcpui/core/testing` ships a fake
84
- host to exercise exposure end to end (and seeds the future inspector):
100
+ No mainstream agent calls WebMCP broadly yet, so `@webmcpui/core/testing` ships a
101
+ fake host to exercise exposure end to end:
85
102
 
86
103
  ```ts
87
104
  import { installFakeAgent } from '@webmcpui/core/testing';
88
105
 
89
106
  const agent = installFakeAgent();
90
- // ... connect a <wmcp-input expose> ...
107
+ // ... connect a <wmcp-input expose> / <wmcp-button expose> ...
91
108
  await agent.call('fill_email', { value: 'agent@webmcpui.com' });
109
+ await agent.call('click_button');
92
110
  agent.restore();
93
111
  ```
94
112
 
113
+ ## Documentation
114
+
115
+ Full docs, live demos for every element, and `llms.txt` at
116
+ **[webmcpui.com](https://webmcpui.com)**.
117
+
95
118
  ## Build & test
96
119
 
97
120
  ```bash
98
121
  pnpm build # tsup → dist/ (ESM + IIFE + d.ts)
99
122
  pnpm typecheck # tsc --noEmit
100
- pnpm test # @web/test-runner in real Chromium (form association,
101
- # validation a11y, WebMCP exposure)
123
+ pnpm test # @web/test-runner in real Chromium
102
124
  pnpm test:smoke # node smoke check against the built dist
103
125
  ```
104
126
 
105
127
  Tests run in a genuine browser via the Playwright launcher — form-associated
106
- custom elements and ElementInternals don't work under jsdom. First run needs
128
+ custom elements and `ElementInternals` don't work under jsdom. First run needs
107
129
  the browser binary:
108
130
 
109
131
  ```bash
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LitElement, CSSResultGroup, TemplateResult, nothing, CSSResult } from 'lit';
2
- import { J as JSONSchema } from './webmcp-JAn7I2xj.js';
3
- export { T as ToolDisposer, W as WebMCPToolDefinition, a as WebMCPToolResult, b as WebMCPToolResultContent, e as exposeTool, i as isWebMCPAvailable } from './webmcp-JAn7I2xj.js';
2
+ import { J as JSONSchema, W as WebMCPToolResult } from './webmcp-DbmbtX6x.js';
3
+ export { T as ToolDisposer, a as WebMCPToolDefinition, b as WebMCPToolResultContent, e as exposeTool, i as isWebMCPAvailable } from './webmcp-DbmbtX6x.js';
4
4
 
5
5
  /**
6
6
  * Minimal Standard Schema v1 types + a validation helper.
@@ -61,6 +61,51 @@ declare function validateStandard<Output>(schema: StandardSchemaV1<unknown, Outp
61
61
  /** Duck-type check that an unknown value is a Standard Schema. */
62
62
  declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
63
63
 
64
+ /**
65
+ * Base for every element that can register itself as a WebMCP tool — both the
66
+ * *value* controls ({@link WmcpFormControl}) and the *action* elements
67
+ * ({@link WmcpAction}).
68
+ *
69
+ * It owns only the exposure *mechanism*: the `expose` / `tool-name` /
70
+ * `tool-description` opt-in surface, the tool's lifecycle (register on connect,
71
+ * dispose on disconnect, re-register when its identity changes, drop when
72
+ * `expose` is turned off), and the single `exposeTool` call. The spec-sensitive
73
+ * registration/disposal itself lives one layer down in `exposeTool`, so this is
74
+ * thin wiring over a single source of truth.
75
+ *
76
+ * Each subtree supplies the *policy* via four hooks — {@link resolvedToolName},
77
+ * {@link defaultToolDescription}, {@link toolInputSchema}, {@link executeTool} —
78
+ * plus {@link toolReactiveProps} when extra properties feed the tool's name or
79
+ * description. Abstract — not registered on its own.
80
+ */
81
+ declare abstract class WmcpExposable extends LitElement {
82
+ /** Whether to expose this element as a WebMCP tool. */
83
+ expose: boolean;
84
+ /** Override the generated WebMCP tool name. */
85
+ toolName: string;
86
+ /** Override the generated WebMCP tool description. */
87
+ toolDescription: string;
88
+ private toolDisposer;
89
+ /** The resolved WebMCP tool name. */
90
+ abstract get resolvedToolName(): string;
91
+ /** Description used when `tool-description` is not set. */
92
+ protected abstract get defaultToolDescription(): string;
93
+ /** JSON Schema for the tool's args. Defaults to a no-argument tool. */
94
+ protected toolInputSchema(): JSONSchema;
95
+ /** Invoked when the agent calls the tool; returns the agent-facing result. */
96
+ protected abstract executeTool(args: Record<string, unknown>): WebMCPToolResult | Promise<WebMCPToolResult>;
97
+ /**
98
+ * Property changes that alter the tool's *identity* (name/description) and so
99
+ * require re-registration. State read live inside {@link executeTool} does
100
+ * not belong here. Subclasses extend this with their own naming inputs.
101
+ */
102
+ protected get toolReactiveProps(): readonly string[];
103
+ connectedCallback(): void;
104
+ disconnectedCallback(): void;
105
+ updated(changed: Map<string, unknown>): void;
106
+ protected registerTool(): void;
107
+ }
108
+
64
109
  /**
65
110
  * Base class for form-associated, agent-operable controls.
66
111
  *
@@ -86,7 +131,7 @@ declare const textFieldStyles: CSSResult;
86
131
  * that supplies its control markup and tool schema. Abstract — not registered
87
132
  * on its own.
88
133
  */
89
- declare abstract class WmcpFormControl extends LitElement {
134
+ declare abstract class WmcpFormControl extends WmcpExposable {
90
135
  static formAssociated: boolean;
91
136
  static styles: CSSResultGroup;
92
137
  /** Visible label text. */
@@ -102,12 +147,6 @@ declare abstract class WmcpFormControl extends LitElement {
102
147
  helperText: string;
103
148
  /** Message shown when a `required` control is empty. */
104
149
  requiredMessage: string;
105
- /** Whether to expose this control as a WebMCP tool. */
106
- expose: boolean;
107
- /** Override the generated WebMCP tool name. */
108
- toolName: string;
109
- /** Override the generated WebMCP tool description. */
110
- toolDescription: string;
111
150
  /**
112
151
  * Standard Schema validator (Zod, Valibot, ArkType, …). Set as a property,
113
152
  * not an attribute. Validation runs on input and on form validation.
@@ -115,7 +154,6 @@ declare abstract class WmcpFormControl extends LitElement {
115
154
  schema?: StandardSchemaV1<unknown, unknown>;
116
155
  protected error: string;
117
156
  protected readonly internals: ElementInternals;
118
- private toolDisposer;
119
157
  /** Noun used in default tool names/descriptions when `name` is empty. */
120
158
  protected get controlNoun(): string;
121
159
  /** The rendered control element (`<input>` / `<textarea>` / `<select>`). */
@@ -145,9 +183,10 @@ declare abstract class WmcpFormControl extends LitElement {
145
183
  protected get requiredMessageDefault(): string;
146
184
  disconnectedCallback(): void;
147
185
  updated(changed: Map<string, unknown>): void;
148
- /** The resolved WebMCP tool name. */
149
186
  get resolvedToolName(): string;
150
- protected registerTool(): void;
187
+ protected get defaultToolDescription(): string;
188
+ protected get toolReactiveProps(): readonly string[];
189
+ protected executeTool(args: Record<string, unknown>): Promise<WebMCPToolResult>;
151
190
  /**
152
191
  * Apply the agent's tool arguments to component state. Defaults to treating
153
192
  * `args.value` as the new string value; controls with a different shape
@@ -359,13 +398,401 @@ declare class WmcpRadioGroup extends WmcpFormControl {
359
398
  render(): TemplateResult;
360
399
  }
361
400
 
401
+ /**
402
+ * Base class for agent-operable *action* elements.
403
+ *
404
+ * Where {@link WmcpFormControl} exposes a *value* an agent can set, an action
405
+ * element exposes an *action* an agent can trigger — a click, an open, a
406
+ * dismissal. The WebMCP exposure mechanism (the `expose` surface, the tool
407
+ * lifecycle, the `exposeTool` call) lives in the shared {@link WmcpExposable}
408
+ * base; this layer adds the action-flavored *policy*: a verb-prefixed tool name
409
+ * (`click_save`, `open_booking`), an action-shaped default description, and a
410
+ * no-argument tool by default.
411
+ *
412
+ * A subclass supplies {@link actionVerb} and {@link executeTool}, and may
413
+ * override {@link actionNoun}, {@link defaultNameSuffix}, or
414
+ * {@link toolInputSchema} (e.g. a menu whose action takes which-item). Abstract
415
+ * — not registered on its own.
416
+ *
417
+ * Extracted when the second action element (dialog) landed alongside the first
418
+ * (button) — two data points reveal what's genuinely shared, the same rule
419
+ * that produced `WmcpFormControl`.
420
+ */
421
+ declare abstract class WmcpAction extends WmcpExposable {
422
+ /** Identifier used to build the default tool name. */
423
+ name: string;
424
+ /** Accessible name and the noun used in the default tool description. */
425
+ label: string;
426
+ /** Verb that prefixes the default tool name, e.g. `click` → `click_save`. */
427
+ protected abstract get actionVerb(): string;
428
+ /** Suffix for the default tool name when `name` is empty. */
429
+ protected get defaultNameSuffix(): string;
430
+ /** Human-readable noun for the thing being acted on. */
431
+ protected get actionNoun(): string;
432
+ get resolvedToolName(): string;
433
+ protected get defaultToolDescription(): string;
434
+ protected get toolReactiveProps(): readonly string[];
435
+ /**
436
+ * Perform the action and return the agent-facing result. Called when the
437
+ * agent invokes the tool; subclasses implement the actual effect.
438
+ */
439
+ protected abstract executeTool(args: Record<string, unknown>): WebMCPToolResult | Promise<WebMCPToolResult>;
440
+ }
441
+
442
+ /** Visual variants `<wmcp-button>` supports (shadcn-aligned). */
443
+ type WmcpButtonVariant = 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost';
444
+ /** Size variants `<wmcp-button>` supports. */
445
+ type WmcpButtonSize = 'sm' | 'md' | 'lg';
446
+ /** Native button behaviors `<wmcp-button>` mirrors. */
447
+ type WmcpButtonType = 'button' | 'submit' | 'reset';
448
+ /**
449
+ * `<wmcp-button>` — a themeable, agent-operable button. The first Phase 2
450
+ * interaction primitive.
451
+ *
452
+ * Where the form controls expose a *value* an agent can set, a button exposes
453
+ * an *action* an agent can trigger. When a WebMCP host is present and the
454
+ * button opts in (`expose`), it registers a tool that activates the button
455
+ * exactly as a human click would — running light-DOM `click` handlers and, for
456
+ * `type="submit"`/`"reset"`, driving the associated form. With no agent
457
+ * present (the common case today) it is simply a good, accessible button.
458
+ *
459
+ * Activation has a single path: both a real click and an agent tool call route
460
+ * through the inner native `<button>`, so behavior, focus, and form
461
+ * participation are identical for human and agent. A `disabled` button can be
462
+ * activated by neither.
463
+ *
464
+ * Form-associated, so `type="submit"` and `type="reset"` work from inside the
465
+ * shadow boundary (a native submit button there would not reach the outer
466
+ * form). Visible content is slotted; an explicit `label` is used for the
467
+ * accessible name and the default tool description when set.
468
+ *
469
+ * Not auto-registered — call `defineComponents()` (or load the CDN bundle).
470
+ */
471
+ declare class WmcpButton extends WmcpAction {
472
+ static readonly tagName = "wmcp-button";
473
+ static formAssociated: boolean;
474
+ static shadowRootOptions: ShadowRootInit;
475
+ static styles: CSSResultGroup;
476
+ /** Visual variant. */
477
+ variant: WmcpButtonVariant;
478
+ /** Size. */
479
+ size: WmcpButtonSize;
480
+ /** Native button behavior: a plain button, a form submit, or a form reset. */
481
+ type: WmcpButtonType;
482
+ /** Disables the button for both humans and agents. */
483
+ disabled: boolean;
484
+ private readonly internals;
485
+ /** The inner native `<button>` that owns activation, focus, and a11y. */
486
+ private get button();
487
+ protected get actionVerb(): string;
488
+ protected get defaultNameSuffix(): string;
489
+ protected get actionNoun(): string;
490
+ protected get defaultToolDescription(): string;
491
+ protected executeTool(): WebMCPToolResult;
492
+ /**
493
+ * Activate the button as if a human clicked it: routes through the inner
494
+ * native button so light-DOM `click` handlers fire and `type`-driven form
495
+ * submission/reset happens. A no-op when disabled.
496
+ */
497
+ activate(): void;
498
+ /**
499
+ * Inner-button click handler. The native click already bubbles (composed) to
500
+ * the host, so light-DOM listeners fire without help; this only adds the
501
+ * cross-shadow form behavior a native submit/reset button can't do from here.
502
+ */
503
+ private onInnerClick;
504
+ render(): TemplateResult;
505
+ }
506
+
507
+ /**
508
+ * `<wmcp-dialog>` — a modal dialog whose *action* is being opened. The second
509
+ * Phase 2 interaction primitive, and the one that motivated the shared
510
+ * {@link WmcpAction} base.
511
+ *
512
+ * It wraps the native `<dialog>` element, so it inherits the platform's focus
513
+ * trap, top-layer stacking, backdrop, and Escape-to-close for free. The
514
+ * exposed WebMCP action is **open** — the agent surfaces the dialog for the
515
+ * user to review; closing/confirming stays a deliberate human (or
516
+ * programmatic) step. That asymmetry is the consent gate: an agent can ask for
517
+ * attention, but the irreversible confirm is the person's to make.
518
+ *
519
+ * Drive it declaratively with the reflected `open` attribute or imperatively
520
+ * with {@link show} / {@link close}; either way it emits composed `open` and
521
+ * `close` events. With no agent present it is simply an accessible modal.
522
+ *
523
+ * Not auto-registered — call `defineComponents()` (or load the CDN bundle).
524
+ */
525
+ declare class WmcpDialog extends WmcpAction {
526
+ static readonly tagName = "wmcp-dialog";
527
+ static styles: CSSResultGroup;
528
+ /** Whether the dialog is open (reflected, so `[open]` styling works). */
529
+ open: boolean;
530
+ /** Open as a modal (focus-trapping, with backdrop). Set false for non-modal. */
531
+ modal: boolean;
532
+ /** Keep the dialog open when the backdrop is clicked (modal "static" mode). */
533
+ staticBackdrop: boolean;
534
+ /** Last `returnValue` the dialog closed with (mirrors native `<dialog>`). */
535
+ returnValue: string;
536
+ private get dialogEl();
537
+ protected get actionVerb(): string;
538
+ protected get defaultNameSuffix(): string;
539
+ protected get defaultToolDescription(): string;
540
+ protected executeTool(): WebMCPToolResult;
541
+ updated(changed: Map<string, unknown>): void;
542
+ /** Open the dialog (modal unless `modal` is false). */
543
+ show(): void;
544
+ /** Close the dialog, optionally recording a `returnValue`. */
545
+ close(returnValue?: string): void;
546
+ /** Reconcile our `open` state with the native dialog's imperative API. */
547
+ private syncNativeOpen;
548
+ /** Native `close` fires for Escape, backdrop, or our own `close()`. */
549
+ private onNativeClose;
550
+ /**
551
+ * Close on a backdrop click. A click whose target is the `<dialog>` itself
552
+ * is the backdrop — content clicks retarget to `.panel` or its children — so
553
+ * this never fires on the dialog's own frame or a text drag-select.
554
+ */
555
+ private onDialogClick;
556
+ render(): TemplateResult;
557
+ }
558
+
559
+ /** A single selectable item in a `<wmcp-menu>`. */
560
+ interface WmcpMenuItem {
561
+ value: string;
562
+ label: string;
563
+ disabled?: boolean;
564
+ }
565
+ /**
566
+ * `<wmcp-menu>` — a menu-button whose exposed action is *selecting an item*.
567
+ * The first **parameterized** interaction primitive: where button and dialog
568
+ * expose no-argument actions, the menu's tool takes *which item* — an `enum`
569
+ * of the item values — so it pressure-tests the {@link WmcpAction} base the
570
+ * way `<wmcp-select>` did for `WmcpFormControl`.
571
+ *
572
+ * The popup uses the native Popover API (top layer, light-dismiss, and Escape
573
+ * for free); items come from declarative `<option>` children (no build) or the
574
+ * `items` property. Selecting an item — by human click, keyboard, or agent
575
+ * tool call — fires a composed `select` event (`detail: { value, label }`) and
576
+ * closes the menu. A menu dispatches actions; it does not hold a value.
577
+ *
578
+ * Not auto-registered — call `defineComponents()` (or load the CDN bundle).
579
+ */
580
+ declare class WmcpMenu extends WmcpAction {
581
+ static readonly tagName = "wmcp-menu";
582
+ static styles: CSSResultGroup;
583
+ /** Trigger label. */
584
+ label: string;
585
+ /**
586
+ * Items as data. When non-empty, takes precedence over declarative
587
+ * `<option>` children. Set as a property, not an attribute.
588
+ */
589
+ items: WmcpMenuItem[];
590
+ /** Whether the popup is open (reflected). */
591
+ open: boolean;
592
+ /** The normalized item model actually rendered (property or declarative). */
593
+ private resolvedItems;
594
+ private itemObserver?;
595
+ private get menuEl();
596
+ private get itemButtons();
597
+ protected get actionVerb(): string;
598
+ protected get defaultNameSuffix(): string;
599
+ protected get actionNoun(): string;
600
+ protected get defaultToolDescription(): string;
601
+ protected get toolReactiveProps(): readonly string[];
602
+ protected toolInputSchema(): JSONSchema;
603
+ protected executeTool(args: Record<string, unknown>): WebMCPToolResult;
604
+ connectedCallback(): void;
605
+ disconnectedCallback(): void;
606
+ willUpdate(changed: Map<string, unknown>): void;
607
+ private syncItems;
608
+ private readDeclarativeItems;
609
+ /** Dispatch the selection and close the menu. */
610
+ private selectItem;
611
+ private onItemClick;
612
+ /** Sync `open`/`aria-expanded` and move focus into the menu when it opens. */
613
+ private onToggle;
614
+ /** Roving focus among items with the arrow / Home / End keys. */
615
+ private onMenuKeydown;
616
+ render(): TemplateResult;
617
+ }
618
+
619
+ /** A single tab derived from a `[tab]` panel child of `<wmcp-tabs>`. */
620
+ interface WmcpTabInfo {
621
+ value: string;
622
+ label: string;
623
+ disabled?: boolean;
624
+ }
625
+ /**
626
+ * `<wmcp-tabs>` — a tab set whose exposed action is *switching the active tab*.
627
+ * The first **stateful** interaction primitive: where button/dialog/menu are
628
+ * fire-and-forget dispatchers, a tab set holds a persistent `active` selection
629
+ * (reflected), and the agent's tool both reads and changes it.
630
+ *
631
+ * It confirms the {@link WmcpAction} base needs no "current value" concept — a
632
+ * stateful action element simply owns its own state (here `active`, like the
633
+ * dialog owns `open`) and its `executeTool` mutates it.
634
+ *
635
+ * Panels are declarative light-DOM children carrying a `tab` attribute (and an
636
+ * optional `tab-label`); the tablist is derived from them. Switching — by
637
+ * click, arrow keys, or agent — updates `active`, reveals the matching panel,
638
+ * and fires a composed `change` event (`detail: { value }`).
639
+ *
640
+ * Not auto-registered — call `defineComponents()` (or load the CDN bundle).
641
+ */
642
+ declare class WmcpTabs extends WmcpAction {
643
+ static readonly tagName = "wmcp-tabs";
644
+ static styles: CSSResultGroup;
645
+ /** The value of the active tab (reflected — this is the persistent state). */
646
+ active: string;
647
+ /** Accessible name for the tablist. */
648
+ label: string;
649
+ /** The tab model derived from the `[tab]` panel children. */
650
+ private tabs;
651
+ private panelObserver?;
652
+ protected get actionVerb(): string;
653
+ protected get defaultNameSuffix(): string;
654
+ protected get actionNoun(): string;
655
+ protected get defaultToolDescription(): string;
656
+ protected get toolReactiveProps(): readonly string[];
657
+ protected toolInputSchema(): JSONSchema;
658
+ protected executeTool(args: Record<string, unknown>): WebMCPToolResult;
659
+ connectedCallback(): void;
660
+ disconnectedCallback(): void;
661
+ updated(changed: Map<string, unknown>): void;
662
+ /** Light-DOM children that declare a tab via the `tab` attribute. */
663
+ private get panelEls();
664
+ private syncTabs;
665
+ /** Fall back to the first enabled tab when `active` is unset or invalid. */
666
+ private ensureActive;
667
+ /** Reflect the active tab onto the slotted panels (roles, labels, hidden). */
668
+ private applyPanels;
669
+ /** Switch the active tab, firing `change` when it actually moves. */
670
+ switchTo(value: string): void;
671
+ private get tabButtons();
672
+ /** Arrow / Home / End roving with automatic activation (ARIA tabs pattern). */
673
+ private onKeydown;
674
+ render(): TemplateResult;
675
+ }
676
+
677
+ /** Where the panel sits relative to its trigger. */
678
+ type WmcpPopoverPlacement = 'top' | 'bottom' | 'left' | 'right';
679
+ /** How the popover is opened: clicked (interactive) or hovered (tooltip). */
680
+ type WmcpPopoverTrigger = 'click' | 'hover';
681
+ /**
682
+ * `<wmcp-popover>` — a non-modal, anchored floating panel whose exposed action
683
+ * is being opened. The sibling of [dialog](./dialog.ts) (modal) and
684
+ * [menu](./menu.ts) (action list): same native-Popover-API + CSS-anchor
685
+ * machinery as the menu, but the content is arbitrary.
686
+ *
687
+ * `trigger="click"` (default) gives an interactive popover — click to toggle,
688
+ * with light-dismiss and Escape for free. `trigger="hover"` gives a tooltip —
689
+ * it opens on hover/focus, closes on leave/blur, and labels its trigger via
690
+ * `aria-describedby`. Either way the agent can `open` it; closing stays a
691
+ * human/light-dismiss step, the same consent posture as the dialog.
692
+ *
693
+ * Not auto-registered — call `defineComponents()` (or load the CDN bundle).
694
+ */
695
+ declare class WmcpPopover extends WmcpAction {
696
+ static readonly tagName = "wmcp-popover";
697
+ static styles: CSSResultGroup;
698
+ /** Whether the panel is open (reflected). */
699
+ open: boolean;
700
+ /** Trigger text, used when no `trigger` slot content is provided. */
701
+ label: string;
702
+ /** Panel placement relative to the trigger. */
703
+ placement: WmcpPopoverPlacement;
704
+ /** Open on click (interactive popover) or on hover/focus (tooltip). */
705
+ trigger: WmcpPopoverTrigger;
706
+ private get panelEl();
707
+ protected get actionVerb(): string;
708
+ protected get defaultNameSuffix(): string;
709
+ protected get defaultToolDescription(): string;
710
+ protected executeTool(): WebMCPToolResult;
711
+ updated(changed: Map<string, unknown>): void;
712
+ /** Open the popover. */
713
+ show(): void;
714
+ /** Close the popover. */
715
+ close(): void;
716
+ private syncNativeOpen;
717
+ /** Native toggle fires for clicks, light-dismiss, Escape, and our own calls. */
718
+ private onToggle;
719
+ private closeTimer?;
720
+ disconnectedCallback(): void;
721
+ /** Open on hover/focus of the trigger *or* the panel. */
722
+ private openOnHover;
723
+ /**
724
+ * Close on leave/blur — but on a short delay, so the pointer can cross the
725
+ * gap from the trigger to the panel (and reach interactive content inside it)
726
+ * without the popover vanishing. Re-entering either cancels the close.
727
+ */
728
+ private scheduleHoverClose;
729
+ private cancelScheduledClose;
730
+ render(): TemplateResult;
731
+ }
732
+
733
+ /** A toast's severity, used for styling and the agent-readable summary. */
734
+ type WmcpToastVariant = 'info' | 'success' | 'warning' | 'error';
735
+ /** Options accepted by {@link WmcpToast.show}. */
736
+ interface WmcpToastOptions {
737
+ title?: string;
738
+ message: string;
739
+ variant?: WmcpToastVariant;
740
+ /** Auto-dismiss after this many ms; `0` keeps it until dismissed. */
741
+ duration?: number;
742
+ }
743
+ /**
744
+ * `<wmcp-toast>` — a notification region, and the one component whose agent
745
+ * surface is *perceiving* rather than *actuating*.
746
+ *
747
+ * Page code throws toasts the way it always has — `el.show({ message })` — and
748
+ * they announce to screen readers via an `aria-live` region. When `expose` is
749
+ * set, the very same notifications become readable by an agent through a
750
+ * `read_<name>` tool: the machine-readable twin of the `aria-live`
751
+ * announcement. An agent acting on the user's behalf often has no other way to
752
+ * learn an outcome ("Payment declined", an async "Export ready"), so it reads
753
+ * the toasts rather than posting them.
754
+ *
755
+ * Extends {@link WmcpExposable} directly — not {@link WmcpAction} — because the
756
+ * tool reports state instead of triggering an action.
757
+ *
758
+ * Not auto-registered — call `defineComponents()` (or load the CDN bundle).
759
+ */
760
+ declare class WmcpToast extends WmcpExposable {
761
+ static readonly tagName = "wmcp-toast";
762
+ static styles: CSSResultGroup;
763
+ /** Identifier used for the default tool name (`read_<name>`). */
764
+ name: string;
765
+ /** Accessible name for the live region. */
766
+ label: string;
767
+ /** Corner the toasts stack in. */
768
+ placement: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
769
+ /** Default auto-dismiss in ms; `0` keeps toasts until dismissed. */
770
+ duration: number;
771
+ private toasts;
772
+ private recent;
773
+ private nextId;
774
+ private timers;
775
+ /** Show a toast. Returns its id (pass to {@link dismiss}). */
776
+ show(options: WmcpToastOptions): number;
777
+ /** Dismiss a toast by id. */
778
+ dismiss(id: number): void;
779
+ /** Dismiss all visible toasts. */
780
+ clear(): void;
781
+ disconnectedCallback(): void;
782
+ get resolvedToolName(): string;
783
+ protected get defaultToolDescription(): string;
784
+ protected get toolReactiveProps(): readonly string[];
785
+ protected executeTool(): WebMCPToolResult;
786
+ render(): TemplateResult;
787
+ }
788
+
362
789
  /**
363
790
  * Register all webmcpui custom elements. Idempotent — safe to call more than
364
791
  * once and safe alongside the CDN bundle (already-defined tags are skipped).
365
792
  */
366
793
  declare function defineComponents(): void;
367
794
 
368
- export { JSONSchema, StandardSchemaV1, type ValidationOutcome, WmcpCheckbox, WmcpFormControl, WmcpInput, type WmcpInputType, WmcpRadio, WmcpRadioGroup, type WmcpRadioOption, WmcpSelect, type WmcpSelectItem, type WmcpSelectOption, type WmcpSelectOptionGroup, WmcpTextarea, defineComponents, isStandardSchema, textFieldStyles, validateStandard };
795
+ export { JSONSchema, StandardSchemaV1, type ValidationOutcome, WebMCPToolResult, WmcpAction, WmcpButton, type WmcpButtonSize, type WmcpButtonType, type WmcpButtonVariant, WmcpCheckbox, WmcpDialog, WmcpExposable, WmcpFormControl, WmcpInput, type WmcpInputType, WmcpMenu, type WmcpMenuItem, WmcpPopover, type WmcpPopoverPlacement, type WmcpPopoverTrigger, WmcpRadio, WmcpRadioGroup, type WmcpRadioOption, WmcpSelect, type WmcpSelectItem, type WmcpSelectOption, type WmcpSelectOptionGroup, type WmcpTabInfo, WmcpTabs, WmcpTextarea, WmcpToast, type WmcpToastOptions, type WmcpToastVariant, defineComponents, isStandardSchema, textFieldStyles, validateStandard };
369
796
 
370
797
  declare global {
371
798
  interface HTMLElementTagNameMap {
@@ -375,5 +802,11 @@ declare global {
375
802
  'wmcp-checkbox': import('./index.js').WmcpCheckbox;
376
803
  'wmcp-radio': import('./index.js').WmcpRadio;
377
804
  'wmcp-radio-group': import('./index.js').WmcpRadioGroup;
805
+ 'wmcp-button': import('./index.js').WmcpButton;
806
+ 'wmcp-dialog': import('./index.js').WmcpDialog;
807
+ 'wmcp-menu': import('./index.js').WmcpMenu;
808
+ 'wmcp-tabs': import('./index.js').WmcpTabs;
809
+ 'wmcp-popover': import('./index.js').WmcpPopover;
810
+ 'wmcp-toast': import('./index.js').WmcpToast;
378
811
  }
379
812
  }