@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.
- package/CHANGELOG.md +437 -0
- package/CONFIGURATION.md +178 -0
- package/LICENSE +21 -0
- package/README.md +237 -0
- package/RELEASE-NOTES.md +77 -0
- package/TEST-PLAN.md +72 -0
- package/USAGE.md +100 -0
- package/package.json +52 -0
- package/src/controls.d.ts +1 -0
- package/src/controls.js +572 -0
- package/src/controls.js.map +1 -0
- package/src/elements/aq-mcp-config.d.ts +289 -0
- package/src/elements/aq-mcp-config.js +673 -0
- package/src/elements/aq-mcp-config.js.map +1 -0
- package/src/elements/aq-mcp-field.d.ts +92 -0
- package/src/elements/aq-mcp-field.js +275 -0
- package/src/elements/aq-mcp-field.js.map +1 -0
- package/src/elements/aq-mcp-section.d.ts +101 -0
- package/src/elements/aq-mcp-section.js +261 -0
- package/src/elements/aq-mcp-section.js.map +1 -0
- package/src/engine/status.d.ts +31 -0
- package/src/engine/status.js +44 -0
- package/src/engine/status.js.map +1 -0
- package/src/engine/submit.d.ts +26 -0
- package/src/engine/submit.js +37 -0
- package/src/engine/submit.js.map +1 -0
- package/src/engine/validate.d.ts +24 -0
- package/src/engine/validate.js +144 -0
- package/src/engine/validate.js.map +1 -0
- package/src/engine/values.d.ts +20 -0
- package/src/engine/values.js +42 -0
- package/src/engine/values.js.map +1 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/mcp-ui-render.css +811 -0
- package/src/registry.d.ts +63 -0
- package/src/registry.js +39 -0
- package/src/registry.js.map +1 -0
- package/src/types.d.ts +239 -0
- package/src/types.js +8 -0
- package/src/types.js.map +1 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
var AqMcpSection_1;
|
|
2
|
+
import { __decorate, __metadata } from "tslib";
|
|
3
|
+
/**
|
|
4
|
+
* `<aq-mcp-section>` — one section rendered as a card (design Option A): header
|
|
5
|
+
* with the section label, an `authType` badge, and a save-target caption
|
|
6
|
+
* ("connector saves · <submitTool>" or "platform saves · applications_update",
|
|
7
|
+
* §13); a body of two-column fields (applicable, non-hidden, in order, §12,
|
|
8
|
+
* and — for `test-connection-report`/`permissions-report` specifically — only
|
|
9
|
+
* once there's actually a value to show, see `hasNothingToShow`); and a save
|
|
10
|
+
* footer whose button emits `section-submit`. A host may register
|
|
11
|
+
* a custom footer for `section.key` via `registerSectionFooter` to replace the
|
|
12
|
+
* default button entirely; absent that, the default "Save <label>" renders.
|
|
13
|
+
* `showFooter` (default true) is a master switch that hides EITHER — custom
|
|
14
|
+
* or default — when false, e.g. when the host drives submission itself.
|
|
15
|
+
* `compact` (set by `<aq-mcp-config>` in `sectionsLayout: 'tabs'` mode) drops
|
|
16
|
+
* the card's own border + title, since the tab nav already shows the label.
|
|
17
|
+
*
|
|
18
|
+
* Light DOM by design (see `<aq-mcp-config>`'s own doc comment) — requires
|
|
19
|
+
* the host to load `mcp-ui-render.css` globally.
|
|
20
|
+
*/
|
|
21
|
+
import { LitElement, html } from 'lit';
|
|
22
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
23
|
+
import { fieldApplies } from '../engine/values.js';
|
|
24
|
+
import { sectionHasEditableFields } from '../engine/submit.js';
|
|
25
|
+
import { resolveSectionFooter } from '../registry.js';
|
|
26
|
+
import './aq-mcp-field.js';
|
|
27
|
+
/**
|
|
28
|
+
* `ui-component`s whose control renders NOTHING when there's no value yet
|
|
29
|
+
* (`test-connection-report`/`permissions-report`'s own "null renders nothing"
|
|
30
|
+
* rule, see `controls.ts`) — distinct from `properties-table`, which always
|
|
31
|
+
* shows its own header/toolbar even with zero rows, so it's deliberately not
|
|
32
|
+
* in this set. Without this filter, `<aq-mcp-field>` still renders its usual
|
|
33
|
+
* label-left/control-right row for one of these with the right column blank —
|
|
34
|
+
* a label dangling next to nothing, e.g. "Validation Report" shown before a
|
|
35
|
+
* host has ever run its test. A field in this set with no value yet is
|
|
36
|
+
* skipped entirely (no `<aq-mcp-field>` element at all, not even an empty
|
|
37
|
+
* one), so nothing — not even the row's own padding/border — is left behind.
|
|
38
|
+
*/
|
|
39
|
+
const HIDE_ENTIRELY_WHEN_EMPTY = new Set(['test-connection-report', 'permissions-report']);
|
|
40
|
+
function hasNothingToShow(field, values) {
|
|
41
|
+
if (!HIDE_ENTIRELY_WHEN_EMPTY.has(field['ui-component']))
|
|
42
|
+
return false;
|
|
43
|
+
const value = values[field.attribute];
|
|
44
|
+
return value === null || value === undefined;
|
|
45
|
+
}
|
|
46
|
+
let AqMcpSection = AqMcpSection_1 = class AqMcpSection extends LitElement {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(...arguments);
|
|
49
|
+
this.values = {};
|
|
50
|
+
this.errors = {};
|
|
51
|
+
/** Host-fed options for `select`/`radio` fields resolved dynamically via `field.tool`
|
|
52
|
+
* (§9.6) — see `<aq-mcp-config>`'s own `dynamicOptions` property for the full contract. */
|
|
53
|
+
this.dynamicOptions = {};
|
|
54
|
+
/** Master switch for this section's footer (§13/§14) — the default "Save
|
|
55
|
+
* <label>" button, AND any `registerSectionFooter` override. Default true;
|
|
56
|
+
* set false to hide the footer entirely regardless of what's registered,
|
|
57
|
+
* e.g. when the host drives submission itself via its own button(s). */
|
|
58
|
+
this.showFooter = true;
|
|
59
|
+
/** Set by `<aq-mcp-config>` when this section is rendered as a tab panel
|
|
60
|
+
* (`sectionsLayout: 'tabs'`) — drops the card's own border and title
|
|
61
|
+
* (the tab nav already shows the label) to avoid double-boxing/duplicate
|
|
62
|
+
* labeling. The authType badge and save-target caption still render. */
|
|
63
|
+
this.compact = false;
|
|
64
|
+
/** Set by `<aq-mcp-config>` when this section is the body of a
|
|
65
|
+
* `nile-accordion` (`sectionsLayout: 'accordion'`). Drops the card AND the
|
|
66
|
+
* whole header — the accordion's own summary already carries the label,
|
|
67
|
+
* authType badge and completion chips — and lays the body out as a
|
|
68
|
+
* two-column grid of label-above-control fields (`dense` is passed down to
|
|
69
|
+
* every `<aq-mcp-field>`). The save-target caption moves into the footer,
|
|
70
|
+
* the only place it still has anything to explain, and only when a footer
|
|
71
|
+
* actually renders. */
|
|
72
|
+
this.dense = false;
|
|
73
|
+
/** Column ceiling for the dense field grid — see `<aq-mcp-config>`'s
|
|
74
|
+
* `accordionColumns`. Only meaningful when `dense` is set. */
|
|
75
|
+
this.columns = 2;
|
|
76
|
+
}
|
|
77
|
+
createRenderRoot() {
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
caption() {
|
|
81
|
+
return this.section.submitTool
|
|
82
|
+
? `connector saves · ${this.section.submitTool}`
|
|
83
|
+
: 'platform saves · applications_update';
|
|
84
|
+
}
|
|
85
|
+
saveLabel() {
|
|
86
|
+
return `Save ${(this.section.label ?? 'changes').toLowerCase()}`;
|
|
87
|
+
}
|
|
88
|
+
emitSubmit() {
|
|
89
|
+
this.dispatchEvent(new CustomEvent('section-submit', { detail: { sectionKey: this.section.key }, bubbles: true, composed: true }));
|
|
90
|
+
}
|
|
91
|
+
renderFooter(showSave) {
|
|
92
|
+
if (!showSave || !this.showFooter)
|
|
93
|
+
return html ``;
|
|
94
|
+
const custom = resolveSectionFooter(this.section.key);
|
|
95
|
+
if (custom) {
|
|
96
|
+
return custom({ section: this.section, values: this.values, onSubmit: () => this.emitSubmit() });
|
|
97
|
+
}
|
|
98
|
+
return html `<div class="aq-mcp-section__footer">
|
|
99
|
+
${this.dense ? html `<div class="aq-mcp-section__caption">${this.caption()}</div>` : ''}
|
|
100
|
+
<nile-button variant="primary" @click=${() => this.emitSubmit()}>${this.saveLabel()}</nile-button>
|
|
101
|
+
</div>`;
|
|
102
|
+
}
|
|
103
|
+
renderHeader() {
|
|
104
|
+
const s = this.section;
|
|
105
|
+
// Accordion mode: the accordion's own summary is the header. Rendering a
|
|
106
|
+
// second one here would repeat the label and badge inside the panel.
|
|
107
|
+
if (this.dense)
|
|
108
|
+
return html ``;
|
|
109
|
+
const badge = s.authType ? html `<nile-badge variant="info">${s.authType}</nile-badge>` : '';
|
|
110
|
+
if (this.compact) {
|
|
111
|
+
return html `<div class="aq-mcp-section__header aq-mcp-section__header--compact">
|
|
112
|
+
${badge}
|
|
113
|
+
<div class="aq-mcp-section__caption">${this.caption()}</div>
|
|
114
|
+
</div>`;
|
|
115
|
+
}
|
|
116
|
+
return html `<div class="aq-mcp-section__header">
|
|
117
|
+
<div class="aq-mcp-section__title">${s.label} ${badge}</div>
|
|
118
|
+
<div class="aq-mcp-section__caption">${this.caption()}</div>
|
|
119
|
+
</div>`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Split the dense (accordion) body into the three zones the design calls for.
|
|
123
|
+
*
|
|
124
|
+
* The `bootstrap` descriptor has NO layout vocabulary — no group, column,
|
|
125
|
+
* width or span key — and its shape is fixed, so this cannot be declared and
|
|
126
|
+
* has to be derived. It is derived only from data the descriptor already
|
|
127
|
+
* carries, never from the attribute name or anything else guessed:
|
|
128
|
+
*
|
|
129
|
+
* - **toggles** — `datatype: "boolean"`. Last, full width. A switch with a
|
|
130
|
+
* one-line description is a different row shape from a labelled control,
|
|
131
|
+
* which is why these group even though it reorders them.
|
|
132
|
+
* - **defaults** — has a `defaultValue`, is NOT required, and is not a
|
|
133
|
+
* credential. Two-up, below a plain rule. The rule carries NO caption: a
|
|
134
|
+
* heading there ("Defaults you can tune") would be the renderer
|
|
135
|
+
* describing the connector's own fields in words the descriptor never
|
|
136
|
+
* supplied. The separation is conveyed by position and density instead,
|
|
137
|
+
* which needs no invented copy.
|
|
138
|
+
* - **primary** — everything else, full width, first: required fields,
|
|
139
|
+
* credentials, and optional fields the connector gave no default for.
|
|
140
|
+
* These are the ones where the user has to supply something.
|
|
141
|
+
*
|
|
142
|
+
* The `isRequired`/`secret` exclusions matter: a required field that happens
|
|
143
|
+
* to carry a default is still something the user must confirm, and a stored
|
|
144
|
+
* credential is never "a default you can tune" — filing either under that
|
|
145
|
+
* heading would tell the reader they can skip it.
|
|
146
|
+
*
|
|
147
|
+
* This DOES reorder relative to the descriptor's `fields` order, and it is
|
|
148
|
+
* the only layout that does. Order is preserved WITHIN each zone, so a
|
|
149
|
+
* connector's intended sequence still shows through.
|
|
150
|
+
*/
|
|
151
|
+
zones(applied) {
|
|
152
|
+
const primary = [];
|
|
153
|
+
const defaults = [];
|
|
154
|
+
const toggles = [];
|
|
155
|
+
for (const f of applied) {
|
|
156
|
+
if (f.datatype === 'boolean')
|
|
157
|
+
toggles.push(f);
|
|
158
|
+
else if (AqMcpSection_1.isTunableDefault(f))
|
|
159
|
+
defaults.push(f);
|
|
160
|
+
else
|
|
161
|
+
primary.push(f);
|
|
162
|
+
}
|
|
163
|
+
return { primary, defaults, toggles };
|
|
164
|
+
}
|
|
165
|
+
/** See `zones()` — "the connector already chose an answer, and you may
|
|
166
|
+
* leave it alone". `datatype: "none"` (buttons, reports) has no value to
|
|
167
|
+
* default; a required field must still be confirmed; a secret is never a
|
|
168
|
+
* tunable default however it was seeded. */
|
|
169
|
+
static isTunableDefault(f) {
|
|
170
|
+
return (f.datatype !== 'none' &&
|
|
171
|
+
f.defaultValue !== undefined &&
|
|
172
|
+
!f.isRequired &&
|
|
173
|
+
!f.secret &&
|
|
174
|
+
!f.writeOnce);
|
|
175
|
+
}
|
|
176
|
+
renderFields(fields) {
|
|
177
|
+
return fields.map((f) => html `<aq-mcp-field
|
|
178
|
+
class="aq-mcp-field ${this.dense ? 'aq-mcp-field--dense' : ''}"
|
|
179
|
+
data-attr=${f.attribute}
|
|
180
|
+
.field=${f}
|
|
181
|
+
.value=${this.values[f.attribute]}
|
|
182
|
+
.error=${this.errors[f.attribute] ?? null}
|
|
183
|
+
.dense=${this.dense}
|
|
184
|
+
.dynamicOptions=${this.dynamicOptions}
|
|
185
|
+
></aq-mcp-field>`);
|
|
186
|
+
}
|
|
187
|
+
renderDenseBody(applied) {
|
|
188
|
+
const { primary, defaults, toggles } = this.zones(applied);
|
|
189
|
+
const twoUp = this.columns === 2;
|
|
190
|
+
return html `
|
|
191
|
+
${primary.length ? html `<div class="aq-mcp-zone aq-mcp-zone--primary">${this.renderFields(primary)}</div>` : ''}
|
|
192
|
+
${defaults.length
|
|
193
|
+
? html `
|
|
194
|
+
<div class="aq-mcp-zone__rule" role="separator"></div>
|
|
195
|
+
<div class="aq-mcp-zone aq-mcp-zone--defaults ${twoUp ? 'aq-mcp-zone--two-up' : ''}">
|
|
196
|
+
${this.renderFields(defaults)}
|
|
197
|
+
</div>
|
|
198
|
+
`
|
|
199
|
+
: ''}
|
|
200
|
+
${toggles.length ? html `<div class="aq-mcp-zone aq-mcp-zone--toggles">${this.renderFields(toggles)}</div>` : ''}
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
render() {
|
|
204
|
+
const s = this.section;
|
|
205
|
+
if (!s)
|
|
206
|
+
return html ``;
|
|
207
|
+
const applied = (s.fields ?? []).filter((f) => !f.hidden && fieldApplies(f, this.values) && !hasNothingToShow(f, this.values));
|
|
208
|
+
const showSave = sectionHasEditableFields(s, this.values);
|
|
209
|
+
return html `
|
|
210
|
+
<div
|
|
211
|
+
class="aq-mcp-section ${this.compact ? 'aq-mcp-section--compact' : ''} ${this.dense
|
|
212
|
+
? 'aq-mcp-section--dense'
|
|
213
|
+
: ''}"
|
|
214
|
+
data-section=${s.key}
|
|
215
|
+
>
|
|
216
|
+
${this.renderHeader()}
|
|
217
|
+
<div class="aq-mcp-section__body ${this.dense ? 'aq-mcp-section__body--dense' : ''}">
|
|
218
|
+
${this.dense ? this.renderDenseBody(applied) : this.renderFields(applied)}
|
|
219
|
+
</div>
|
|
220
|
+
${this.renderFooter(showSave)}
|
|
221
|
+
</div>
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
__decorate([
|
|
226
|
+
property({ attribute: false }),
|
|
227
|
+
__metadata("design:type", Object)
|
|
228
|
+
], AqMcpSection.prototype, "section", void 0);
|
|
229
|
+
__decorate([
|
|
230
|
+
property({ attribute: false }),
|
|
231
|
+
__metadata("design:type", Object)
|
|
232
|
+
], AqMcpSection.prototype, "values", void 0);
|
|
233
|
+
__decorate([
|
|
234
|
+
property({ attribute: false }),
|
|
235
|
+
__metadata("design:type", Object)
|
|
236
|
+
], AqMcpSection.prototype, "errors", void 0);
|
|
237
|
+
__decorate([
|
|
238
|
+
property({ attribute: false }),
|
|
239
|
+
__metadata("design:type", Object)
|
|
240
|
+
], AqMcpSection.prototype, "dynamicOptions", void 0);
|
|
241
|
+
__decorate([
|
|
242
|
+
property({ type: Boolean }),
|
|
243
|
+
__metadata("design:type", Object)
|
|
244
|
+
], AqMcpSection.prototype, "showFooter", void 0);
|
|
245
|
+
__decorate([
|
|
246
|
+
property({ type: Boolean }),
|
|
247
|
+
__metadata("design:type", Object)
|
|
248
|
+
], AqMcpSection.prototype, "compact", void 0);
|
|
249
|
+
__decorate([
|
|
250
|
+
property({ type: Boolean }),
|
|
251
|
+
__metadata("design:type", Object)
|
|
252
|
+
], AqMcpSection.prototype, "dense", void 0);
|
|
253
|
+
__decorate([
|
|
254
|
+
property({ type: Number }),
|
|
255
|
+
__metadata("design:type", Number)
|
|
256
|
+
], AqMcpSection.prototype, "columns", void 0);
|
|
257
|
+
AqMcpSection = AqMcpSection_1 = __decorate([
|
|
258
|
+
customElement('aq-mcp-section')
|
|
259
|
+
], AqMcpSection);
|
|
260
|
+
export { AqMcpSection };
|
|
261
|
+
//# sourceMappingURL=aq-mcp-section.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aq-mcp-section.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/elements/aq-mcp-section.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section-completeness engine — the derived data behind `sectionsLayout:
|
|
3
|
+
* 'accordion'`'s per-card attention state. A collapsed section shows nothing
|
|
4
|
+
* of its contents, so something has to answer "is there anything left to do in
|
|
5
|
+
* here" without opening it; that answer is currently rendered as the card's
|
|
6
|
+
* tinted edge rather than as header text.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately distinct from `<aq-mcp-config>`'s own `sectionHasError()`,
|
|
9
|
+
* which reads recorded validation errors: a required field the user simply
|
|
10
|
+
* hasn't reached yet is INCOMPLETE, not INVALID. This module reports
|
|
11
|
+
* completeness (has a value been supplied), the error machinery keeps
|
|
12
|
+
* reporting validity (is the supplied value acceptable) — they answer
|
|
13
|
+
* different questions and neither replaces the other.
|
|
14
|
+
*/
|
|
15
|
+
import type { Section, Values } from '../types.js';
|
|
16
|
+
/** What the accordion header renders from. */
|
|
17
|
+
export interface SectionStatus {
|
|
18
|
+
/** Applicable, non-hidden, value-bearing fields. */
|
|
19
|
+
fieldCount: number;
|
|
20
|
+
requiredEmpty: number;
|
|
21
|
+
optionalEmpty: number;
|
|
22
|
+
/**
|
|
23
|
+
* - `incomplete` — at least one required field has no value (needs attention)
|
|
24
|
+
* - `complete` — every counted field has a value
|
|
25
|
+
* - `required-set` — every REQUIRED field has a value, some optional ones don't
|
|
26
|
+
* - `neutral` — nothing to count (a section of buttons/reports only)
|
|
27
|
+
*/
|
|
28
|
+
state: 'complete' | 'required-set' | 'incomplete' | 'neutral';
|
|
29
|
+
}
|
|
30
|
+
/** Summarise one section against the current values. Pure — no element state. */
|
|
31
|
+
export declare function sectionStatus(section: Section, values: Values): SectionStatus;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fieldApplies } from './values.js';
|
|
2
|
+
import { isEmpty } from './validate.js';
|
|
3
|
+
import { WRITE_ONCE_UNCHANGED } from './submit.js';
|
|
4
|
+
/**
|
|
5
|
+
* A field counts toward a section's status when it can actually hold a value
|
|
6
|
+
* the user is expected to supply: same "is this in play" rule
|
|
7
|
+
* `<aq-mcp-config>`'s `fieldNeedsValidation()` uses, so the chip can never
|
|
8
|
+
* claim a section is complete while a field it skipped is failing validation.
|
|
9
|
+
* `datatype: 'none'` (buttons, `test-connection-report`, `permissions-report`,
|
|
10
|
+
* `properties-table`) is excluded — those never hold a submitted value, and
|
|
11
|
+
* counting them would inflate "N fields" with things the user can't fill in.
|
|
12
|
+
*/
|
|
13
|
+
function counts(field, values) {
|
|
14
|
+
return field.datatype !== 'none' && !field.hidden && fieldApplies(field, values);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A stored write-once secret (§7) is SET, even though its recorded value is
|
|
18
|
+
* only the sentinel rather than the real secret — the whole point of the
|
|
19
|
+
* sentinel is "the host already has this on record." Treating it as empty
|
|
20
|
+
* would flag every saved credentials section as incomplete forever.
|
|
21
|
+
*/
|
|
22
|
+
function hasValue(value) {
|
|
23
|
+
return value === WRITE_ONCE_UNCHANGED || !isEmpty(value);
|
|
24
|
+
}
|
|
25
|
+
/** Summarise one section against the current values. Pure — no element state. */
|
|
26
|
+
export function sectionStatus(section, values) {
|
|
27
|
+
let fieldCount = 0;
|
|
28
|
+
let requiredEmpty = 0;
|
|
29
|
+
let optionalEmpty = 0;
|
|
30
|
+
for (const field of section?.fields ?? []) {
|
|
31
|
+
if (!counts(field, values))
|
|
32
|
+
continue;
|
|
33
|
+
fieldCount += 1;
|
|
34
|
+
if (hasValue(values[field.attribute]))
|
|
35
|
+
continue;
|
|
36
|
+
if (field.isRequired)
|
|
37
|
+
requiredEmpty += 1;
|
|
38
|
+
else
|
|
39
|
+
optionalEmpty += 1;
|
|
40
|
+
}
|
|
41
|
+
const state = fieldCount === 0 ? 'neutral' : requiredEmpty > 0 ? 'incomplete' : optionalEmpty === 0 ? 'complete' : 'required-set';
|
|
42
|
+
return { fieldCount, requiredEmpty, optionalEmpty, state };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/engine/status.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAiBnD;;;;;;;;GAQG;AACH,SAAS,MAAM,CAAC,KAAY,EAAE,MAAc;IAC1C,OAAO,KAAK,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACnF,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,oBAAoB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,MAAc;IAC5D,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC;YAAE,SAAS;QACrC,UAAU,IAAI,CAAC,CAAC;QAChB,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAAE,SAAS;QAChD,IAAI,KAAK,CAAC,UAAU;YAAE,aAAa,IAAI,CAAC,CAAC;;YACpC,aAAa,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,KAAK,GACT,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC;IAEtH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit assembly (§14) + save-target resolution (§13).
|
|
3
|
+
*
|
|
4
|
+
* Values keyed by `attribute`, only for fields that applied. `datatype:"none"`
|
|
5
|
+
* and hidden fields submit nothing (hidden omitted = unchanged). A blank
|
|
6
|
+
* password/`writeOnce` field is omitted (= keep current). Numbers are coerced.
|
|
7
|
+
* `submitTool` (per section) vs the platform default is carried on the payload;
|
|
8
|
+
* the library never calls the tool itself.
|
|
9
|
+
*/
|
|
10
|
+
import type { Section, Values } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Sentinel a host seeds as a `writeOnce` field's value to mean "a value is
|
|
13
|
+
* stored, unchanged". The field renders masked + "Replace" (§7) and submit omits
|
|
14
|
+
* it. Once the user clicks Replace and types, the real value replaces it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const WRITE_ONCE_UNCHANGED = "__aq_write_once_unchanged__";
|
|
17
|
+
export interface SubmitPayload {
|
|
18
|
+
sectionKey: string;
|
|
19
|
+
/** Connector tool to call, or null → platform default (`applications_update`) (§13). */
|
|
20
|
+
submitTool: string | null;
|
|
21
|
+
values: Values;
|
|
22
|
+
}
|
|
23
|
+
/** Build the submit payload for one section (§13/§14). */
|
|
24
|
+
export declare function collectSubmit(section: Section, values: Values): SubmitPayload;
|
|
25
|
+
/** True when a section has at least one editable (value-bearing, visible, applied) field. */
|
|
26
|
+
export declare function sectionHasEditableFields(section: Section, values: Values): boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { fieldApplies } from './values.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sentinel a host seeds as a `writeOnce` field's value to mean "a value is
|
|
4
|
+
* stored, unchanged". The field renders masked + "Replace" (§7) and submit omits
|
|
5
|
+
* it. Once the user clicks Replace and types, the real value replaces it.
|
|
6
|
+
*/
|
|
7
|
+
export const WRITE_ONCE_UNCHANGED = '__aq_write_once_unchanged__';
|
|
8
|
+
function coerce(field, value) {
|
|
9
|
+
if (field.datatype === 'number') {
|
|
10
|
+
const n = Number(value);
|
|
11
|
+
return Number.isNaN(n) ? value : n;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
/** Build the submit payload for one section (§13/§14). */
|
|
16
|
+
export function collectSubmit(section, values) {
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const field of section.fields ?? []) {
|
|
19
|
+
if (field.datatype === 'none' || field.hidden)
|
|
20
|
+
continue;
|
|
21
|
+
if (!fieldApplies(field, values))
|
|
22
|
+
continue;
|
|
23
|
+
const v = values[field.attribute];
|
|
24
|
+
const isSecretEntry = field['ui-component'] === 'password' || field.writeOnce;
|
|
25
|
+
if (isSecretEntry && (v == null || v === '' || v === WRITE_ONCE_UNCHANGED))
|
|
26
|
+
continue; // blank/unchanged
|
|
27
|
+
if (v === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
out[field.attribute] = coerce(field, v);
|
|
30
|
+
}
|
|
31
|
+
return { sectionKey: section.key, submitTool: section.submitTool ?? null, values: out };
|
|
32
|
+
}
|
|
33
|
+
/** True when a section has at least one editable (value-bearing, visible, applied) field. */
|
|
34
|
+
export function sectionHasEditableFields(section, values) {
|
|
35
|
+
return (section.fields ?? []).some((f) => f.datatype !== 'none' && !f.hidden && fieldApplies(f, values));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=submit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submit.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/engine/submit.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAElE,SAAS,MAAM,CAAC,KAAY,EAAE,KAAc;IAC1C,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AASD,0DAA0D;AAC1D,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,MAAc;IAC5D,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM;YAAE,SAAS;QACxD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC;YAAE,SAAS;QAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,UAAU,IAAI,KAAK,CAAC,SAAS,CAAC;QAC9E,IAAI,aAAa,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,oBAAoB,CAAC;YAAE,SAAS,CAAC,kBAAkB;QACxG,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAC1F,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,wBAAwB,CAAC,OAAgB,EAAE,MAAc;IACvE,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CACrE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation engine (bootstrap contract §8). The engine owns the *logic* and
|
|
3
|
+
* returns a message; the Nile control owns *display* (the caller pushes the
|
|
4
|
+
* message into the control's `error`/`errorMessage`) — the split from D-7.
|
|
5
|
+
*
|
|
6
|
+
* Rules: patterns are ECMAScript regex sources, matched against the whole value,
|
|
7
|
+
* AND-combined. Emptiness is governed by `isRequired`, not regex. A regex that
|
|
8
|
+
* fails to compile is a descriptor defect — logged and skipped, never blocking.
|
|
9
|
+
*/
|
|
10
|
+
import type { Field } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* "Has this field got a value at all" — the single emptiness notion in this
|
|
13
|
+
* library. Exported (not just used by `validateField`'s `isRequired` check)
|
|
14
|
+
* so `engine/status.ts`'s section-completeness counts mean exactly the same
|
|
15
|
+
* thing the required-check means: a whitespace-only string, `[]` and `{}` are
|
|
16
|
+
* empty; `false` (an off toggle) and a `File` are NOT — they are values a user
|
|
17
|
+
* chose. Two separate emptiness rules would let a section read "Complete"
|
|
18
|
+
* while a field it contains still fails validation.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isEmpty(value: unknown): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a field's value. Returns the message to show, or null when valid.
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateField(field: Field, value: unknown): string | null;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { WRITE_ONCE_UNCHANGED } from './submit.js';
|
|
2
|
+
const REQUIRED_MESSAGE = 'This field is required.';
|
|
3
|
+
/**
|
|
4
|
+
* "Has this field got a value at all" — the single emptiness notion in this
|
|
5
|
+
* library. Exported (not just used by `validateField`'s `isRequired` check)
|
|
6
|
+
* so `engine/status.ts`'s section-completeness counts mean exactly the same
|
|
7
|
+
* thing the required-check means: a whitespace-only string, `[]` and `{}` are
|
|
8
|
+
* empty; `false` (an off toggle) and a `File` are NOT — they are values a user
|
|
9
|
+
* chose. Two separate emptiness rules would let a section read "Complete"
|
|
10
|
+
* while a field it contains still fails validation.
|
|
11
|
+
*/
|
|
12
|
+
export function isEmpty(value) {
|
|
13
|
+
if (value == null)
|
|
14
|
+
return true;
|
|
15
|
+
if (typeof value === 'string')
|
|
16
|
+
return value.trim() === '';
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return value.length === 0;
|
|
19
|
+
if (typeof File !== 'undefined' && value instanceof File)
|
|
20
|
+
return false; // a File has no own enumerable keys
|
|
21
|
+
if (typeof value === 'object')
|
|
22
|
+
return Object.keys(value).length === 0;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
/** `accept` match against a File's MIME type or filename extension (§8, `file` datatype). */
|
|
26
|
+
function acceptMatches(accept, file) {
|
|
27
|
+
return accept.some((a) => {
|
|
28
|
+
if (a.startsWith('.'))
|
|
29
|
+
return file.name.toLowerCase().endsWith(a.toLowerCase());
|
|
30
|
+
if (a.endsWith('/*'))
|
|
31
|
+
return file.type.startsWith(a.slice(0, -1));
|
|
32
|
+
return file.type === a;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function validateFile(field, file, msg) {
|
|
36
|
+
// `value` reaching here is only ever SUPPOSED to be a real File — but a stale
|
|
37
|
+
// value from before the field became a file-upload type, or any other bad
|
|
38
|
+
// input the host hands back, is a plain object/string with no `.name`/`.size`.
|
|
39
|
+
// Fail safe (invalid, same as any other malformed value) instead of crashing.
|
|
40
|
+
if (!(typeof File !== 'undefined' && file instanceof File))
|
|
41
|
+
return msg;
|
|
42
|
+
if (field.maxSizeKB != null && file.size > field.maxSizeKB * 1024)
|
|
43
|
+
return msg;
|
|
44
|
+
if (field.accept?.length && !acceptMatches(field.accept, file))
|
|
45
|
+
return msg;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function compile(patterns) {
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const p of patterns ?? []) {
|
|
51
|
+
try {
|
|
52
|
+
out.push(new RegExp(p));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.warn(`[mcp-ui-render] invalid regex "${p}": field treated as unvalidated (§8).`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function allMatch(regexes, s) {
|
|
62
|
+
return regexes.every((r) => r.test(s));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate a field's value. Returns the message to show, or null when valid.
|
|
66
|
+
*/
|
|
67
|
+
export function validateField(field, value) {
|
|
68
|
+
// A `writeOnce` field's already-stored value is this sentinel string, never
|
|
69
|
+
// the real value (§7) — validating it against the field's own rules is
|
|
70
|
+
// meaningless (it's not what will be submitted) and, for `file` fields,
|
|
71
|
+
// actively crashes: `validateFile` below reads `.name`/`.size` expecting a
|
|
72
|
+
// real `File`, which a plain string doesn't have. Short-circuit before any
|
|
73
|
+
// datatype-specific check ever sees it, same as `<aq-mcp-field>`'s own
|
|
74
|
+
// `isSetWriteOnce()` already treats this sentinel as "stored, don't touch."
|
|
75
|
+
if (value === WRITE_ONCE_UNCHANGED)
|
|
76
|
+
return null;
|
|
77
|
+
const dt = field.datatype;
|
|
78
|
+
if (dt === 'none')
|
|
79
|
+
return null;
|
|
80
|
+
if (isEmpty(value))
|
|
81
|
+
return field.isRequired ? REQUIRED_MESSAGE : null;
|
|
82
|
+
if (dt === 'boolean')
|
|
83
|
+
return null;
|
|
84
|
+
const msg = field.validationmessage ?? 'Invalid value.';
|
|
85
|
+
const regexes = compile(field.validation);
|
|
86
|
+
if (field['ui-component'] === 'file') {
|
|
87
|
+
// Covers both single-file (`datatype: "file"`) and multi-file (`datatype:
|
|
88
|
+
// "array"` + `ui-component: "file"`) — binary content, so `validation`
|
|
89
|
+
// regex doesn't apply; only `minItems`/`maxItems` (multi) + `accept`/`maxSizeKB` (§8).
|
|
90
|
+
const files = Array.isArray(value) ? value : [value];
|
|
91
|
+
if (dt === 'array') {
|
|
92
|
+
if (field.minItems != null && files.length < field.minItems)
|
|
93
|
+
return msg;
|
|
94
|
+
if (field.maxItems != null && files.length > field.maxItems)
|
|
95
|
+
return msg;
|
|
96
|
+
}
|
|
97
|
+
return files.every((f) => validateFile(field, f, msg) === null) ? null : msg;
|
|
98
|
+
}
|
|
99
|
+
switch (dt) {
|
|
100
|
+
case 'string':
|
|
101
|
+
return regexes.length && !allMatch(regexes, String(value)) ? msg : null;
|
|
102
|
+
case 'number': {
|
|
103
|
+
if (regexes.length && !allMatch(regexes, String(value)))
|
|
104
|
+
return msg;
|
|
105
|
+
const n = Number(value);
|
|
106
|
+
if (!Number.isNaN(n)) {
|
|
107
|
+
if (field.min != null && n < field.min)
|
|
108
|
+
return msg;
|
|
109
|
+
if (field.max != null && n > field.max)
|
|
110
|
+
return msg;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
case 'array': {
|
|
115
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
116
|
+
if (field.minItems != null && arr.length < field.minItems)
|
|
117
|
+
return msg;
|
|
118
|
+
if (field.maxItems != null && arr.length > field.maxItems)
|
|
119
|
+
return msg;
|
|
120
|
+
if (regexes.length && !arr.every((el) => allMatch(regexes, String(el))))
|
|
121
|
+
return msg;
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
case 'object': {
|
|
125
|
+
const obj = typeof value === 'object' && value ? value : {};
|
|
126
|
+
for (const k of field.requiredKeys ?? []) {
|
|
127
|
+
if (!(k in obj))
|
|
128
|
+
return msg;
|
|
129
|
+
}
|
|
130
|
+
if (regexes.length && !Object.values(obj).every((v) => allMatch(regexes, String(v))))
|
|
131
|
+
return msg;
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
default: {
|
|
135
|
+
// Unknown datatype → treat as string, warn if required (§9.2).
|
|
136
|
+
if (field.isRequired) {
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.warn(`[mcp-ui-render] unknown datatype "${String(dt)}" on required field "${field.attribute}": validating as string (§9.2).`);
|
|
139
|
+
}
|
|
140
|
+
return regexes.length && !allMatch(regexes, String(value)) ? msg : null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../../apps/mcp-ui-render/src/engine/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value-model helpers: dependency applicability (§12) and default seeding.
|
|
3
|
+
* Unknown keys already survive because `Values` is an open record and updates
|
|
4
|
+
* are shallow merges — nothing here drops properties it doesn't recognise (§9.4).
|
|
5
|
+
*/
|
|
6
|
+
import type { Descriptor, Field, Values } from '../types.js';
|
|
7
|
+
export declare function asArray(v: string | string[] | undefined): string[];
|
|
8
|
+
/**
|
|
9
|
+
* A field applies unless it declares at least one dependency condition — either
|
|
10
|
+
* `dependencyAttribute`/`dependencyValue` (single attribute) or `dependsOn` (a list
|
|
11
|
+
* of attribute/value conditions, §9.7) — and NONE of them currently match. With
|
|
12
|
+
* both present, or multiple `dependsOn` entries, they're OR'd together: any one
|
|
13
|
+
* condition matching is enough (e.g. an FTP-server picker relevant when EITHER
|
|
14
|
+
* `sourceConnectionType === 'directConnection'` OR `storageType === 'sftpStorage'`).
|
|
15
|
+
* A field with no dependency declared at all always applies (§12). Non-applying
|
|
16
|
+
* fields are skipped and their stored value dropped by the caller.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fieldApplies(field: Field, values: Values): boolean;
|
|
19
|
+
/** Seed `defaultValue` for applied, value-bearing fields that have no value yet (§12). */
|
|
20
|
+
export declare function seedDefaults(descriptor: Descriptor, values: Values): Values;
|