@aquera/mcp-ui-render 0.0.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +437 -0
  2. package/CONFIGURATION.md +178 -0
  3. package/LICENSE +21 -0
  4. package/README.md +237 -0
  5. package/RELEASE-NOTES.md +77 -0
  6. package/TEST-PLAN.md +72 -0
  7. package/USAGE.md +100 -0
  8. package/package.json +52 -0
  9. package/src/controls.d.ts +1 -0
  10. package/src/controls.js +572 -0
  11. package/src/controls.js.map +1 -0
  12. package/src/elements/aq-mcp-config.d.ts +289 -0
  13. package/src/elements/aq-mcp-config.js +673 -0
  14. package/src/elements/aq-mcp-config.js.map +1 -0
  15. package/src/elements/aq-mcp-field.d.ts +92 -0
  16. package/src/elements/aq-mcp-field.js +275 -0
  17. package/src/elements/aq-mcp-field.js.map +1 -0
  18. package/src/elements/aq-mcp-section.d.ts +101 -0
  19. package/src/elements/aq-mcp-section.js +261 -0
  20. package/src/elements/aq-mcp-section.js.map +1 -0
  21. package/src/engine/status.d.ts +31 -0
  22. package/src/engine/status.js +44 -0
  23. package/src/engine/status.js.map +1 -0
  24. package/src/engine/submit.d.ts +26 -0
  25. package/src/engine/submit.js +37 -0
  26. package/src/engine/submit.js.map +1 -0
  27. package/src/engine/validate.d.ts +24 -0
  28. package/src/engine/validate.js +144 -0
  29. package/src/engine/validate.js.map +1 -0
  30. package/src/engine/values.d.ts +20 -0
  31. package/src/engine/values.js +42 -0
  32. package/src/engine/values.js.map +1 -0
  33. package/src/index.d.ts +18 -0
  34. package/src/index.js +20 -0
  35. package/src/index.js.map +1 -0
  36. package/src/mcp-ui-render.css +811 -0
  37. package/src/registry.d.ts +63 -0
  38. package/src/registry.js +39 -0
  39. package/src/registry.js.map +1 -0
  40. package/src/types.d.ts +239 -0
  41. package/src/types.js +8 -0
  42. package/src/types.js.map +1 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * `<aq-mcp-config>` — root element. Owns the descriptor + values + validation
3
+ * state, renders ordered sections (§12), and emits intent for the host:
4
+ * - `aq-mcp-change` — on any field edit `{ attribute, value, values }`
5
+ * - `aq-mcp-submit` — per-section save `{ sectionKey, submitTool, values }` (§13/§14)
6
+ * - `aq-mcp-action` — button/action `{ tool, args, confirm, field, action }` (§5).
7
+ * `action` is the specific `FieldAction` clicked for a multi-action control's
8
+ * own toolbar (e.g. `properties-table`'s "Download"/"Edit Configuration"),
9
+ * undefined for the classic single-action `button` control.
10
+ * - `aq-mcp-options-request` — a `select`/`radio` field with `tool` set has no
11
+ * options yet `{ attribute, tool, args }` (§9.6); host resolves + feeds them
12
+ * back via the `dynamicOptions` property (see below), one request per attribute.
13
+ * It performs NO network I/O and holds no secrets (D-4): it renders, validates,
14
+ * and emits; the host executes tools. A host driving its OWN "submit
15
+ * everything" button (bypassing per-section footers) can call the public
16
+ * `validateAll()` method directly on the element to get the library's real
17
+ * validation + inline field-error display instead of just the raw `values`.
18
+ * Every field is also validated live on its own edits (`onFieldInput`), not
19
+ * just on submit — its error updates the instant its value changes.
20
+ * `sectionsLayout` ('stacked' default, or 'tabs' with `tabsPlacement` for
21
+ * orientation) controls how sections are arranged — everything else
22
+ * (validation, submit, footers) behaves identically in either layout.
23
+ * `tabsShowErrorIndicator` (default true, tabs mode only) puts a red dot on a
24
+ * tab whose section has a required-but-empty or invalid field, computed live
25
+ * — not just after a failed submit.
26
+ *
27
+ * Renders into LIGHT DOM (`createRenderRoot()` returns `this`, no shadow
28
+ * root) on all three elements in this package, deliberately: it lets Nile's
29
+ * CSS custom-property tokens (`--ng-*`/`--nile-*`) and `mcp-ui-render.css`
30
+ * cascade in from the host's own stylesheet the same way any other page
31
+ * content does, with no per-shadow-root style injection needed. The
32
+ * trade-off is zero style encapsulation — a host's global CSS can affect
33
+ * `.aq-mcp-*` classes and vice versa — and it means `mcp-ui-render.css` MUST
34
+ * be loaded globally by the host; without it these elements render
35
+ * structurally correct but entirely unstyled, with no error or warning
36
+ * (see USAGE.md "Styling & light DOM").
37
+ */
38
+ import { LitElement, type PropertyValues, type TemplateResult } from 'lit';
39
+ import type { Descriptor, Values } from '../types.js';
40
+ import './aq-mcp-section.js';
41
+ export declare class AqMcpConfig extends LitElement {
42
+ /** The `bootstrap` response to render. */
43
+ descriptor: Descriptor | null;
44
+ /** Current values keyed by `attribute`. */
45
+ values: Values;
46
+ /**
47
+ * Host-fed options for `select`/`radio` fields whose list is resolved dynamically
48
+ * (`field.tool` set, e.g. `listFtpServers`) rather than declared statically as
49
+ * `field.options` (§9.6) — keyed by `field.attribute`. This library performs no
50
+ * network I/O itself (D-4): it only ASKS, via `aq-mcp-options-request`; the host
51
+ * calls the tool and sets the result back in here (immutably — a new object, not
52
+ * a mutated one, so Lit's property-changed check fires):
53
+ *
54
+ * el.addEventListener('aq-mcp-options-request', async (e) => {
55
+ * const { attribute, tool, args } = e.detail;
56
+ * const options = await host.callTool(tool, args); // e.g. FTP server list
57
+ * el.dynamicOptions = { ...el.dynamicOptions, [attribute]: options };
58
+ * });
59
+ *
60
+ * Takes priority over `field.options` when both are present for the same
61
+ * attribute. A field is requested at most once per attribute for this
62
+ * element's lifetime — a host that needs to force a refresh re-triggers it
63
+ * itself (out of scope here, same "host decides" split as `validateAll()`).
64
+ */
65
+ dynamicOptions: Record<string, unknown[]>;
66
+ /** Master switch for every section's footer (§13/§14) — the default "Save
67
+ * <label>" button, AND any `registerSectionFooter` override. Default true;
68
+ * set false to hide every section's footer at once, e.g. when the host
69
+ * drives submission itself via its own button(s). */
70
+ showSectionFooters: boolean;
71
+ /** How sections are laid out. `'stacked'` (default) is today's behavior —
72
+ * every section as its own card, one after another. `'tabs'` renders one
73
+ * `nile-nav-tab` per section (nav) with the section's own card as that
74
+ * tab's panel — orientation controlled by `tabsPlacement`. `'accordion'`
75
+ * renders one `nile-accordion` per section, single-open, with a completion
76
+ * summary in each collapsed header (field count + status chip) and a
77
+ * two-column, label-above-control body — see `renderAccordion`. */
78
+ sectionsLayout: 'stacked' | 'tabs' | 'accordion';
79
+ /** Tab orientation when `sectionsLayout === 'tabs'` — passed straight
80
+ * through to `nile-nav-tab-group`'s own `placement`. Ignored in
81
+ * `'stacked'` mode. `'start'` gives a left-hand vertical tab list. */
82
+ tabsPlacement: 'top' | 'bottom' | 'start' | 'end';
83
+ /** In `'tabs'` mode, show a small red dot next to a tab's label when that
84
+ * section has a required-but-empty field or a live validation error — lets
85
+ * the user spot which tab needs attention without opening it. Default
86
+ * true (this is a pure addition with no prior equivalent, so there's
87
+ * nothing to stay backward-compatible with); set false to opt out.
88
+ * Ignored in `'stacked'` mode, where inline field errors are already
89
+ * visible without needing a tab-level summary. */
90
+ tabsShowErrorIndicator: boolean;
91
+ /** Which section starts expanded in `'accordion'` mode. Left unset
92
+ * (`null`), the library picks: the first section with a required-but-empty
93
+ * field, else the first visible section — so a form always opens on the
94
+ * part that still needs work. A section `key` pins that section instead;
95
+ * the empty string starts every section collapsed. Only consulted when the
96
+ * descriptor (or this property) changes — never re-evaluated on a value
97
+ * edit, which would slam the card the user is typing in shut. Ignored in
98
+ * the other layouts. */
99
+ accordionOpenSection: string | null;
100
+ /** Show the open section's save footer in `'accordion'` mode. Default
101
+ * FALSE, unlike the other two layouts: an accordion is a whole-form
102
+ * overview, and the host normally drives one submit for all of it via
103
+ * `validateAll()`. Set true to get the per-section "Save <label>" button
104
+ * here too; `showSectionFooters` still overrides both to off. Ignored in
105
+ * the other layouts. */
106
+ accordionShowFooters: boolean;
107
+ /** How many columns the open section's field grid uses in `'accordion'`
108
+ * mode. `2` (default) is the side-by-side layout; `1` stacks every field
109
+ * full width, which suits a narrow container, a long-label descriptor, or
110
+ * simply a host that prefers a single reading column.
111
+ *
112
+ * This is a CEILING, not a fixed count: at `2` the grid still collapses to
113
+ * one column when the container is too narrow to give each cell a usable
114
+ * width (see `.aq-mcp-section__body--dense`), so `1` means "never two",
115
+ * not "always exactly one regardless of space". Ignored in the other
116
+ * layouts, whose rows are single-column by construction. */
117
+ accordionColumns: 1 | 2;
118
+ /** Host override for section header icons in `'accordion'` mode, keyed by
119
+ * `section.key` → `nile-glyph` name. Highest priority in a three-step
120
+ * fallback: this map, then the descriptor's own `section.icon`, then
121
+ * `DEFAULT_SECTION_ICONS` below. The host wins because it knows the
122
+ * product's iconography; the descriptor only knows the connector. */
123
+ sectionIcons: Record<string, string>;
124
+ private errors;
125
+ /** The single expanded section's `key` in `'accordion'` mode, or null when
126
+ * all are collapsed. Holding ONE key is what makes the accordion
127
+ * single-open: setting it re-renders every sibling with `open=false`. */
128
+ private openKey;
129
+ private pendingAction;
130
+ /** Attributes already asked about via `aq-mcp-options-request` — a plain dedupe
131
+ * guard, not rendered state, so mutated in place rather than reassigned (no
132
+ * need to trigger a Lit update for it). */
133
+ private requestedOptions;
134
+ protected createRenderRoot(): HTMLElement;
135
+ protected willUpdate(changed: PropertyValues<this>): void;
136
+ /** The section to expand first: an explicit `accordionOpenSection` wins
137
+ * (including `''` for "all collapsed"), else the first section still
138
+ * missing a required value, else the first visible one. */
139
+ private resolveOpenKey;
140
+ /** Sections in render order (§12) — `order` ascending, missing treated as 0. */
141
+ private sortedSections;
142
+ protected updated(changed: PropertyValues<this>): void;
143
+ /** Emits `aq-mcp-options-request` for every currently-applicable `select`/`radio`
144
+ * field that declares `tool` but has no options yet — neither a static
145
+ * `field.options` nor an already-fed `dynamicOptions` entry — and hasn't been
146
+ * asked about before. Skipped for a field that's statically hidden or whose
147
+ * `dependencyAttribute` condition isn't currently met (§12) — no point asking
148
+ * for FTP servers before the user has even chosen SFTP storage. */
149
+ private requestPendingOptions;
150
+ /** Look up a field's own schema by `attribute` — needed to re-validate it
151
+ * live on every edit (`onFieldInput`), since the event only carries the
152
+ * new value, not the field definition itself. */
153
+ private fieldByAttribute;
154
+ /** Whether a field is currently in play for validation — skips
155
+ * `datatype: "none"` (buttons/reports never hold a value), statically
156
+ * hidden fields, and fields whose `dependencyAttribute` condition isn't
157
+ * currently met. Shared by `computeErrors`, live on-change validation, and
158
+ * the tab error-indicator, so all three agree on what "applicable" means. */
159
+ private fieldNeedsValidation;
160
+ private onFieldInput;
161
+ private onFieldAction;
162
+ private onSectionSubmit;
163
+ private emitAction;
164
+ /** Validate one batch of fields against the current `values` — shared by
165
+ * `validateSection` (one section) and the public `validateAll` (every
166
+ * section), so both paths compute errors identically. */
167
+ private computeErrors;
168
+ /** Does this section have an actual, currently-recorded validation error —
169
+ * i.e. a field present in `this.errors` with a message. Deliberately reads
170
+ * `this.errors`, NOT a fresh re-validation of every field: a required field
171
+ * the user simply hasn't reached yet is incomplete, not invalid, and
172
+ * shouldn't flag its tab before the user has done anything. An error only
173
+ * lands in `this.errors` once a field has actually been edited (live
174
+ * validation) or the section/form has been submitted at least once —
175
+ * exactly the same "has this field's error actually surfaced" signal the
176
+ * inline per-field error display already uses. */
177
+ private sectionHasError;
178
+ private validateSection;
179
+ private submitSection;
180
+ /** Validate every section's applicable fields at once and return the full
181
+ * form state. Pushes results into this element's own `errors` state — the
182
+ * same thing a section's Save button does via `validateSection`, just
183
+ * across ALL sections at once — so a host-driven "submit everything"
184
+ * button (bypassing per-section footers entirely) still gets the
185
+ * library's real validation and inline field-error display, not just the
186
+ * raw `values`. Call it imperatively on the element (e.g. an Angular
187
+ * template ref: `<aq-mcp-config #cfg>` then `cfg.validateAll()`) — there's
188
+ * no natural event to hang this off, since the host decides what "submit
189
+ * everything" even means for its own data shape. */
190
+ validateAll(): {
191
+ valid: boolean;
192
+ values: Values;
193
+ };
194
+ render(): TemplateResult;
195
+ /** Dispatch to the layout the host asked for. `'stacked'` — today's
196
+ * default — is the fallback for any unrecognised value, so a typo'd
197
+ * attribute renders a usable form rather than nothing. */
198
+ private renderSections;
199
+ /** A section whose every field is statically hidden has nothing to show
200
+ * (§7, §12) — in 'stacked' mode `renderSection` just renders nothing for
201
+ * it; in 'tabs' mode it must not get a tab at all (an empty tab is worse
202
+ * than no tab), so this filters the nav list up front. */
203
+ private visibleSections;
204
+ private renderTabs;
205
+ /**
206
+ * Is this `nile-show`/`nile-hide` the ACCORDION's own, or one that bubbled up
207
+ * from a descendant?
208
+ *
209
+ * Every Nile component emits with `bubbles: true, composed: true`
210
+ * (`internal/nile-element.ts`), and `nile-tooltip` emits the same
211
+ * `nile-show`/`nile-hide` names an accordion does. A field's help tooltip
212
+ * lives inside the open card, so without this guard simply hovering an info
213
+ * icon opened the section and dismissing the tooltip COLLAPSED it — the
214
+ * card slamming shut under the user's cursor.
215
+ *
216
+ * `currentTarget` is the `nile-accordion` the listener sits on;
217
+ * `target` is whatever emitted. Comparing them keeps a descendant's
218
+ * identically-named event from driving this element's open state, and stays
219
+ * correct for any future nested Nile component that reuses these names.
220
+ */
221
+ private static isOwnDisclosure;
222
+ /**
223
+ * `sectionsLayout: 'accordion'` — one `nile-accordion` per visible section,
224
+ * single-open, in §12 order.
225
+ *
226
+ * Single-open falls out of holding ONE key: `nile-accordion` owns its own
227
+ * `open` flag, so both directions are wired — `?open` down from `openKey`,
228
+ * `nile-show`/`nile-hide` back up. Setting `openKey` on a show re-renders
229
+ * every sibling with `open=false`, which closes them; there's no need to
230
+ * imperatively call `hide()` on the others.
231
+ */
232
+ /** Host map first, then the built-in map for the contract's own section
233
+ * keys, then a generic glyph. The descriptor has no icon field and its
234
+ * shape is fixed, so the host is the only place a product can override
235
+ * this — which is the right place anyway: iconography is the app's
236
+ * decision, not the connector's. */
237
+ private iconFor;
238
+ /**
239
+ * The header's completion readout, for a section the user may never open.
240
+ *
241
+ * A mark and a NUMBER, no sentence. The count is descriptor-derived (how
242
+ * many `isRequired` fields still hold no value); the check and the dot are
243
+ * symbols. An earlier version read "Complete" and "N required left", which
244
+ * is the renderer narrating state in words the descriptor never supplied.
245
+ *
246
+ * The trade-off is real and worth naming: a bare dot-and-count is terser
247
+ * than "2 required left", and leans on the reader connecting it to the `*`
248
+ * marks inside the card. `title` carries the count on hover and to assistive
249
+ * tech using the descriptor's own key name rather than invented prose.
250
+ */
251
+ private renderSectionStatus;
252
+ /**
253
+ * `sectionsLayout: 'accordion'` — one `nile-accordion` per visible section,
254
+ * single-open, in §12 order. Each header is an icon tile, the section
255
+ * `label`, its `authType` badge, and a completion status beside the chevron,
256
+ * so a collapsed section says what it is and whether it still needs work
257
+ * without being opened.
258
+ *
259
+ * Every word of connector-describing text here is a descriptor VALUE
260
+ * rendered verbatim — `section.label` and `section.authType`. An earlier
261
+ * version composed a sentence around them ("Basic authentication · saved by
262
+ * the platform"), which read as the connector's own description of itself
263
+ * but was the renderer putting words in its mouth: only "Basic" came from
264
+ * the descriptor, and "saved by the platform" was inferred from a key being
265
+ * ABSENT. The `bootstrap` format carries no prose field, so rather than
266
+ * invent one, nothing is written here that the descriptor did not supply.
267
+ *
268
+ * Single-open falls out of holding ONE key: `nile-accordion` owns its own
269
+ * `open` flag, so both directions are wired — `?open` down from `openKey`,
270
+ * `nile-show`/`nile-hide` back up. Setting `openKey` on a show re-renders
271
+ * every sibling with `open=false`, which closes them; there's no need to
272
+ * imperatively call `hide()` on the others.
273
+ */
274
+ private renderAccordion;
275
+ /** `compact` drops the per-section card's own border + title (the tab nav
276
+ * already shows the label) when nested inside a tab panel — the authType
277
+ * badge and save-target caption still show either way. `dense` is the
278
+ * accordion equivalent: the card AND the header go (the accordion's own
279
+ * summary carries all of it), and the body switches to the two-column,
280
+ * label-above-control grid. They're separate flags, not one enum, because
281
+ * each drops a different amount of chrome. */
282
+ private renderSection;
283
+ private renderConfirm;
284
+ }
285
+ declare global {
286
+ interface HTMLElementTagNameMap {
287
+ 'aq-mcp-config': AqMcpConfig;
288
+ }
289
+ }