@golemui/gui-mcp 0.15.0 β 0.16.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/CHANGELOG.md +14 -0
- package/README.md +43 -4
- package/index.js +467 -40
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## 0.15.1 (2026-05-27)
|
|
2
|
+
|
|
3
|
+
This was a version bump only for gui-mcp to align it with other projects, there were no code changes.
|
|
4
|
+
|
|
5
|
+
## 0.15.0 (2026-05-26)
|
|
6
|
+
|
|
7
|
+
### π©Ή Fixes
|
|
8
|
+
|
|
9
|
+
- release from 0.0.0 ([#134](https://github.com/golemui/golemui/pull/134))
|
|
10
|
+
|
|
11
|
+
### β€οΈ Thank You
|
|
12
|
+
|
|
13
|
+
- Mud Scientist
|
|
14
|
+
- RaΓΊl JimΓ©nez @Elecash
|
package/README.md
CHANGED
|
@@ -91,14 +91,53 @@ version-locked to a specific GolemUI release. A CI check in this monorepo
|
|
|
91
91
|
against the exact same schema definitions as `@golemui/gui-*@X.Y.Z`.
|
|
92
92
|
|
|
93
93
|
No LLM calls happen inside this server β every tool is deterministic. The MCP is the
|
|
94
|
-
|
|
94
|
+
_grounding layer_ the host IDE's model calls into.
|
|
95
95
|
|
|
96
96
|
## Development
|
|
97
97
|
|
|
98
|
+
### Interactive testing with MCP Inspector
|
|
99
|
+
|
|
100
|
+
Start the MCP server and open a local web UI at http://localhost:5173
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm run start:mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
To use the server from within Claude Code conversations, register with local Claude Code:
|
|
107
|
+
|
|
108
|
+
#### With the Calude code CLI
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
claude mcp add golemui-local -- node /Users/{USER}/{...}/golem/golemui/dist/libs/gui/mcp/index.js
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then restart or reload the session. The tools appear in Claude's tool list.
|
|
115
|
+
|
|
116
|
+
Remove with claude mcp remove golemui-local when done.
|
|
117
|
+
|
|
118
|
+
#### With the Calude code extension
|
|
119
|
+
|
|
120
|
+
Create or edit the project-level MCP config at .mcp.json in the workspace root:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcpServers": {
|
|
125
|
+
"golemui-local": {
|
|
126
|
+
"command": "node",
|
|
127
|
+
"args": ["/Users/{USER}/{...}/golem/golemui/dist/libs/gui/mcp/index.js"]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then reload the VS Code window (Cmd+Shift+P -> "Developer: Reload Window"). The 4 tools will appear in Claude's tool list for this workspace.
|
|
134
|
+
|
|
135
|
+
Remove the entry from .mcp.json when done.
|
|
136
|
+
|
|
137
|
+
### Other development commands
|
|
138
|
+
|
|
98
139
|
```bash
|
|
99
|
-
#
|
|
100
|
-
npx nx run gui-mcp:build # build to dist/libs/gui/mcp
|
|
101
|
-
npx nx run gui-mcp:vite:test # run the test suite
|
|
140
|
+
npx nx run gui-mcp:vite:test # run the test suite
|
|
102
141
|
npm run sync:mcp-schemas # refresh bundled schemas from libs/gui/shared
|
|
103
142
|
npm run check:mcp-schemas # CI mode β exits non-zero if out of sync
|
|
104
143
|
```
|
package/index.js
CHANGED
|
@@ -133,7 +133,7 @@ function jsonSchemaToGui(schema, opts = {}) {
|
|
|
133
133
|
}
|
|
134
134
|
] : fields;
|
|
135
135
|
return {
|
|
136
|
-
formDefinition: { form: wrapped },
|
|
136
|
+
formDefinition: { $schema: "https://golemui.com/schemas/form.schema.json", form: wrapped },
|
|
137
137
|
unmapped
|
|
138
138
|
};
|
|
139
139
|
}
|
|
@@ -283,7 +283,13 @@ function buildArrayField(path, _name, label, schema, required2, unmapped) {
|
|
|
283
283
|
const required22 = new Set(items.required ?? []);
|
|
284
284
|
const childParent = `${path}.items`;
|
|
285
285
|
for (const [childName, childSchema] of Object.entries(items.properties ?? {})) {
|
|
286
|
-
const w = mapProperty(
|
|
286
|
+
const w = mapProperty(
|
|
287
|
+
childName,
|
|
288
|
+
unwrap(childSchema),
|
|
289
|
+
required22.has(childName),
|
|
290
|
+
childParent,
|
|
291
|
+
unmapped
|
|
292
|
+
);
|
|
287
293
|
if (w) templateChildren.push(w);
|
|
288
294
|
}
|
|
289
295
|
if (!templateChildren.length) {
|
|
@@ -363,7 +369,7 @@ function cleanFields(o) {
|
|
|
363
369
|
const $schema$u = "https://json-schema.org/draft/2020-12/schema";
|
|
364
370
|
const $id$u = "https://golemui.com/schemas/common.schema.json";
|
|
365
371
|
const title$u = "Golem Common Definitions";
|
|
366
|
-
const $defs$t = { "reactiveExpression": { "type": "string", "description": "A reactive expression string. Use `$form` to access form data e.g. `$form.termsAccepted === true`" }, "dotPath": { "type": "string", "description": "A dot-separated path to a property in the form data" }, "dateRange": { "type": "object", "description": "A single date range used by calendar/date-picker widgets to mark days as disabled.", "properties": { "start": { "type": "string" }, "end": { "type": "string" } }, "required": ["start"], "additionalProperties": false }, "uid": { "type": "string", "description": "Unique identifier for the widget" }, "localizable": { "oneOf": [{ "type": "string" }, { "type": "object", "properties": { "key": { "type": "string" }, "default": { "type": "string" }, "params": { "type": "object" } }, "required": ["key"], "additionalProperties": false }] }, "include": { "oneOf": [{ "type": "object", "properties": { "in": { "type": "array", "items": { "type": "string" } } }, "required": ["in"], "additionalProperties": false }, { "type": "object", "properties": { "when": { "$ref": "#/$defs/reactiveExpression" } }, "required": ["when"], "additionalProperties": false }] }, "exclude": { "oneOf": [{ "type": "object", "properties": { "from": { "type": "array", "items": { "type": "string" } } }, "required": ["from"], "additionalProperties": false }, { "type": "object", "properties": { "when": { "$ref": "#/$defs/reactiveExpression" } }, "required": ["when"], "additionalProperties": false }] }, "boolOrWhen": { "oneOf": [{ "type": "boolean" }, { "type": "object", "properties": { "when": { "$ref": "#/$defs/reactiveExpression" } }, "required": ["when"], "additionalProperties": false }] }, "on": { "type": "object", "properties": { "load": { "type": "string" }, "click": { "type": "string" }, "change": { "type": "string" }, "filter": { "type": "string" }, "blur": { "type": "string" } }, "patternProperties": { "^load\\.[^.]+$": { "type": "string" }, "^click\\.[^.]+$": { "type": "string" }, "^change\\.[^.]+$": { "type": "string" }, "^filter\\.[^.]+$": { "type": "string" }, "^blur\\.[^.]+$": { "type": "string" } }, "additionalProperties": false }, "baseWidget": { "type": "object", "properties": { "$schema": { "type": "string" }, "uid": { "$ref": "#/$defs/uid" }, "type": { "type": "string" }, "size": { "type": "number" }, "include": { "$ref": "#/$defs/include" }, "exclude": { "$ref": "#/$defs/exclude" } }, "required": ["type"] } };
|
|
372
|
+
const $defs$t = { "reactiveExpression": { "type": "string", "description": "A reactive expression string. Use `$form` to access form data e.g. `$form.termsAccepted === true`" }, "dotPath": { "type": "string", "description": "A dot-separated path to a property in the form data" }, "dateRange": { "type": "object", "description": "A single date range used by calendar/date-picker widgets to mark days as disabled.", "properties": { "start": { "type": "string", "description": "ISO-8601 date string for the start of the disabled range (inclusive)" }, "end": { "type": "string", "description": "ISO-8601 date string for the end of the disabled range (inclusive). Omit to disable a single day." } }, "required": ["start"], "additionalProperties": false }, "uid": { "type": "string", "description": "Unique identifier for the widget" }, "localizable": { "description": "A value that can be a plain string or an i18n key object. Use a plain string for static text, or `{key, default, params}` to reference a translation key.", "oneOf": [{ "type": "string" }, { "type": "object", "properties": { "key": { "type": "string", "description": "The i18n translation key" }, "default": { "type": "string", "description": "Fallback text shown when the translation key is not found" }, "params": { "type": "object", "description": "Interpolation parameters passed to the translation function" } }, "required": ["key"], "additionalProperties": false }] }, "include": { "description": "Show this widget only in certain form states (`{in: [stateNames]}`) or when a reactive expression is true (`{when: expr}`). If omitted the widget is always shown.", "oneOf": [{ "type": "object", "properties": { "in": { "type": "array", "items": { "type": "string" }, "description": "List of state names (defined in the form `states` map) in which this widget should be visible" } }, "required": ["in"], "additionalProperties": false }, { "type": "object", "properties": { "when": { "$ref": "#/$defs/reactiveExpression" } }, "required": ["when"], "additionalProperties": false }] }, "exclude": { "description": "Hide this widget in certain form states (`{from: [stateNames]}`) or when a reactive expression is true (`{when: expr}`). If omitted the widget is never hidden.", "oneOf": [{ "type": "object", "properties": { "from": { "type": "array", "items": { "type": "string" }, "description": "List of state names (defined in the form `states` map) in which this widget should be hidden" } }, "required": ["from"], "additionalProperties": false }, { "type": "object", "properties": { "when": { "$ref": "#/$defs/reactiveExpression" } }, "required": ["when"], "additionalProperties": false }] }, "boolOrWhen": { "description": "A static boolean or a conditional `{when: reactiveExpression}` object evaluated at runtime. Use `{when: '$form.someField === true'}` for dynamic behavior.", "oneOf": [{ "type": "boolean" }, { "type": "object", "properties": { "when": { "$ref": "#/$defs/reactiveExpression" } }, "required": ["when"], "additionalProperties": false }] }, "on": { "type": "object", "description": 'Event handlers for this widget. Values are action names dispatched to the form\'s event bus. Each key also accepts a state-suffixed variant (`"click.<stateName>"`) that overrides the base handler when the named state is active.', "properties": { "load": { "type": "string", "description": "Action dispatched when the widget is first mounted" }, "click": { "type": "string", "description": "Action dispatched when the user clicks the widget" }, "change": { "type": "string", "description": "Action dispatched when the widget value changes" }, "filter": { "type": "string", "description": "Action dispatched on search/filter input, typically used with async data sources" }, "blur": { "type": "string", "description": "Action dispatched when the widget loses focus" } }, "patternProperties": { "^load\\.[^.]+$": { "type": "string", "description": 'State-suffixed load handler: used instead of `load` when the named state is active (e.g. `"load.myState"`)' }, "^click\\.[^.]+$": { "type": "string", "description": 'State-suffixed click handler: used instead of `click` when the named state is active (e.g. `"click.myState"`)' }, "^change\\.[^.]+$": { "type": "string", "description": 'State-suffixed change handler: used instead of `change` when the named state is active (e.g. `"change.myState"`)' }, "^filter\\.[^.]+$": { "type": "string", "description": 'State-suffixed filter handler: used instead of `filter` when the named state is active (e.g. `"filter.myState"`)' }, "^blur\\.[^.]+$": { "type": "string", "description": 'State-suffixed blur handler: used instead of `blur` when the named state is active (e.g. `"blur.myState"`)' } }, "additionalProperties": false }, "baseWidget": { "type": "object", "description": 'Base properties shared by all widgets. Root-level props that support state-suffixing (e.g. `"label.myState"`) are: `label`, `disabled`, `readonly`, `validator`, `size`. Any key inside `props` can also be suffixed. State names must be declared in the form `states` map.', "properties": { "$schema": { "type": "string" }, "uid": { "$ref": "#/$defs/uid" }, "type": { "type": "string" }, "size": { "type": "number", "description": "Column span in the form grid (1β12). Defaults to full width when omitted.", "minimum": 1, "maximum": 12 }, "include": { "$ref": "#/$defs/include" }, "exclude": { "$ref": "#/$defs/exclude" } }, "required": ["type"] } };
|
|
367
373
|
const commonSchema = {
|
|
368
374
|
$schema: $schema$u,
|
|
369
375
|
$id: $id$u,
|
|
@@ -374,10 +380,10 @@ const $schema$t = "https://json-schema.org/draft/2020-12/schema";
|
|
|
374
380
|
const $id$t = "https://golemui.com/schemas/form.schema.json";
|
|
375
381
|
const title$t = "Golem Form DSL";
|
|
376
382
|
const type$r = "object";
|
|
377
|
-
const properties$r = { "$schema": { "type": "string" }, "states": { "type": "object", "additionalProperties": { "$ref": "common.schema.json#/$defs/reactiveExpression" } }, "form": { "type": "array", "items": { "$ref": "#/$defs/formWidget" } }, "autocomplete": { "type": "string" } };
|
|
383
|
+
const properties$r = { "$schema": { "type": "string" }, "states": { "type": "object", "description": 'Named boolean conditions keyed by state name, each mapping to a reactive expression. States serve two purposes: (1) gating widget visibility via `include.in` / `exclude.from`; (2) overriding individual widget properties when a state is active by appending `".<stateName>"` to any property key β e.g. `"label.myState": "Active label"` or `"props.hint.myState": "Active hint"`. Root-level suffixable props: `label`, `disabled`, `readonly`, `validator`, `size`. Any key inside `props` can also be suffixed.', "additionalProperties": { "$ref": "common.schema.json#/$defs/reactiveExpression" } }, "form": { "type": "array", "description": "The ordered list of widgets that make up the form body", "items": { "$ref": "#/$defs/formWidget" } }, "autocomplete": { "type": "string", "description": "HTML `autocomplete` attribute applied to the `<form>` element (e.g. `on`, `off`)" } };
|
|
378
384
|
const required$r = ["form"];
|
|
379
385
|
const additionalProperties = false;
|
|
380
|
-
const $defs$s = { "chunkRef": { "type": "object", "properties": { "$ref": { "type": "string" } }, "required": ["$ref"], "additionalProperties": false }, "formWidget": { "oneOf": [{ "$ref": "./components/accordion.schema.json" }, { "$ref": "./components/alert.schema.json" }, { "$ref": "./components/button.schema.json" }, { "$ref": "./components/calendar.schema.json" }, { "$ref": "./components/checkbox.schema.json" }, { "$ref": "./components/currency.schema.json" }, { "$ref": "./components/dateinput.schema.json" }, { "$ref": "./components/datepicker.schema.json" }, { "$ref": "./components/dropdown.schema.json" }, { "$ref": "./components/list.schema.json" }, { "$ref": "./components/markdown.schema.json" }, { "$ref": "./components/markdowntext.schema.json" }, { "$ref": "./components/number.schema.json" }, { "$ref": "./components/password.schema.json" }, { "$ref": "./components/radiogroup.schema.json" }, { "$ref": "./components/rangecalendar.schema.json" }, { "$ref": "./components/rangedateinput.schema.json" }, { "$ref": "./components/rangedatepicker.schema.json" }, { "$ref": "./components/repeater.schema.json" }, { "$ref": "./components/select.schema.json" }, { "$ref": "./components/flex.schema.json" }, { "$ref": "./components/grid.schema.json" }, { "$ref": "./components/tabs.schema.json" }, { "$ref": "./components/tags.schema.json" }, { "$ref": "./components/textarea.schema.json" }, { "$ref": "./components/textinput.schema.json" }, { "$ref": "./components/toggle.schema.json" }, { "$ref": "#/$defs/chunkRef" }] } };
|
|
386
|
+
const $defs$s = { "chunkRef": { "type": "object", "description": "A reference to an external chunk file whose widget array will be inlined at this position in the form", "properties": { "$ref": { "type": "string", "description": "Relative path to a JSON chunk file" } }, "required": ["$ref"], "additionalProperties": false }, "formWidget": { "oneOf": [{ "$ref": "./components/accordion.schema.json" }, { "$ref": "./components/alert.schema.json" }, { "$ref": "./components/button.schema.json" }, { "$ref": "./components/calendar.schema.json" }, { "$ref": "./components/checkbox.schema.json" }, { "$ref": "./components/currency.schema.json" }, { "$ref": "./components/dateinput.schema.json" }, { "$ref": "./components/datepicker.schema.json" }, { "$ref": "./components/dropdown.schema.json" }, { "$ref": "./components/list.schema.json" }, { "$ref": "./components/markdown.schema.json" }, { "$ref": "./components/markdowntext.schema.json" }, { "$ref": "./components/number.schema.json" }, { "$ref": "./components/password.schema.json" }, { "$ref": "./components/radiogroup.schema.json" }, { "$ref": "./components/rangecalendar.schema.json" }, { "$ref": "./components/rangedateinput.schema.json" }, { "$ref": "./components/rangedatepicker.schema.json" }, { "$ref": "./components/repeater.schema.json" }, { "$ref": "./components/select.schema.json" }, { "$ref": "./components/flex.schema.json" }, { "$ref": "./components/grid.schema.json" }, { "$ref": "./components/tabs.schema.json" }, { "$ref": "./components/tags.schema.json" }, { "$ref": "./components/textarea.schema.json" }, { "$ref": "./components/textinput.schema.json" }, { "$ref": "./components/toggle.schema.json" }, { "$ref": "#/$defs/chunkRef" }] } };
|
|
381
387
|
const formSchema = {
|
|
382
388
|
$schema: $schema$t,
|
|
383
389
|
$id: $id$t,
|
|
@@ -401,7 +407,7 @@ const layoutWidgetSchema = {
|
|
|
401
407
|
const $schema$r = "https://json-schema.org/draft/2020-12/schema";
|
|
402
408
|
const $id$r = "https://golemui.com/schemas/validators.schema.json";
|
|
403
409
|
const title$r = "GolemUI Validator Definitions";
|
|
404
|
-
const $defs$r = { "localizable": { "$ref": "./common.schema.json#/$defs/localizable" }, "stringValidator": { "type": "object", "properties": { "type": { "const": "string" }, "required": { "type": "boolean" }, "minLength": { "type": "number" }, "maxLength": { "type": "number" }, "pattern": { "type": "string", "description": "A regular expression pattern the value must match" }, "format": { "type": "string", "enum": ["email", "hostname", "ipv4", "ipv6", "url", "uuid", "date", "time", "date-time", "duration"] }, "const": {}, "enum": { "type": "array" }, "messages": { "type": "object", "properties": { "invalid": { "$ref": "#/$defs/localizable" }, "required": { "$ref": "#/$defs/localizable" }, "minLength": { "$ref": "#/$defs/localizable" }, "maxLength": { "$ref": "#/$defs/localizable" }, "pattern": { "$ref": "#/$defs/localizable" }, "format": { "$ref": "#/$defs/localizable" }, "enum": { "$ref": "#/$defs/localizable" }, "const": { "$ref": "#/$defs/localizable" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "numberValidator": { "type": "object", "properties": { "type": { "type": "string", "enum": ["number", "integer"] }, "required": { "type": "boolean" }, "minimum": { "type": "number" }, "maximum": { "type": "number" }, "exclusiveMinimum": { "type": "number" }, "exclusiveMaximum": { "type": "number" }, "multipleOf": { "type": "number" }, "const": {}, "enum": { "type": "array" }, "messages": { "type": "object", "properties": { "invalid": { "$ref": "#/$defs/localizable" }, "minimum": { "$ref": "#/$defs/localizable" }, "maximum": { "$ref": "#/$defs/localizable" }, "exclusiveMinimum": { "$ref": "#/$defs/localizable" }, "exclusiveMaximum": { "$ref": "#/$defs/localizable" }, "multipleOf": { "$ref": "#/$defs/localizable" }, "enum": { "$ref": "#/$defs/localizable" }, "const": { "$ref": "#/$defs/localizable" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "booleanValidator": { "type": "object", "properties": { "type": { "const": "boolean" }, "required": { "type": "boolean" }, "const": {}, "messages": { "type": "object", "properties": { "invalid": { "$ref": "#/$defs/localizable" }, "const": { "$ref": "#/$defs/localizable" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "arrayValidator": { "type": "object", "properties": { "type": { "const": "array" }, "required": { "type": "boolean" }, "minItems": { "type": "number" }, "maxItems": { "type": "number" }, "uniqueItems": { "type": "boolean" }, "messages": { "type": "object", "properties": { "invalid": { "$ref": "#/$defs/localizable" }, "required": { "$ref": "#/$defs/localizable" }, "minItems": { "$ref": "#/$defs/localizable" }, "maxItems": { "$ref": "#/$defs/localizable" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "customValidator": { "type": "object", "properties": { "type": { "const": "custom" }, "required": { "type": "boolean" } }, "required": ["type"], "additionalProperties": true }, "validator": { "oneOf": [{ "$ref": "#/$defs/stringValidator" }, { "$ref": "#/$defs/numberValidator" }, { "$ref": "#/$defs/booleanValidator" }, { "$ref": "#/$defs/arrayValidator" }, { "$ref": "#/$defs/customValidator" }] } };
|
|
410
|
+
const $defs$r = { "localizable": { "$ref": "./common.schema.json#/$defs/localizable" }, "stringValidator": { "type": "object", "properties": { "type": { "const": "string" }, "required": { "type": "boolean", "description": "Makes the field mandatory β validation fails if the value is empty or absent" }, "minLength": { "type": "number", "description": "Minimum number of characters the string must contain" }, "maxLength": { "type": "number", "description": "Maximum number of characters the string may contain" }, "pattern": { "type": "string", "description": "A regular expression pattern the value must match" }, "format": { "type": "string", "description": "A named format the string must conform to (e.g. `email`, `url`, `date`, `uuid`)", "enum": ["email", "hostname", "ipv4", "ipv6", "url", "uuid", "date", "time", "date-time", "duration"] }, "const": { "description": "The string must equal exactly this value" }, "enum": { "type": "array", "description": "The string must be one of these allowed values" }, "messages": { "type": "object", "description": "Custom error messages that override the default text for each constraint violation", "properties": { "invalid": { "$ref": "#/$defs/localizable", "description": "Shown when the value fails general type validation" }, "required": { "$ref": "#/$defs/localizable", "description": "Shown when a required field is empty" }, "minLength": { "$ref": "#/$defs/localizable", "description": "Shown when the value is shorter than `minLength`" }, "maxLength": { "$ref": "#/$defs/localizable", "description": "Shown when the value is longer than `maxLength`" }, "pattern": { "$ref": "#/$defs/localizable", "description": "Shown when the value does not match the `pattern` regex" }, "format": { "$ref": "#/$defs/localizable", "description": "Shown when the value does not conform to the specified `format`" }, "enum": { "$ref": "#/$defs/localizable", "description": "Shown when the value is not in the `enum` list" }, "const": { "$ref": "#/$defs/localizable", "description": "Shown when the value does not equal `const`" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "numberValidator": { "type": "object", "properties": { "type": { "type": "string", "enum": ["number", "integer"] }, "required": { "type": "boolean", "description": "Makes the field mandatory β validation fails if the value is absent" }, "minimum": { "type": "number", "description": "The value must be greater than or equal to this number (inclusive)" }, "maximum": { "type": "number", "description": "The value must be less than or equal to this number (inclusive)" }, "exclusiveMinimum": { "type": "number", "description": "The value must be strictly greater than this number (exclusive)" }, "exclusiveMaximum": { "type": "number", "description": "The value must be strictly less than this number (exclusive)" }, "multipleOf": { "type": "number", "description": "The value must be a multiple of this number" }, "const": { "description": "The value must equal exactly this number" }, "enum": { "type": "array", "description": "The value must be one of these allowed numbers" }, "messages": { "type": "object", "description": "Custom error messages that override the default text for each constraint violation", "properties": { "invalid": { "$ref": "#/$defs/localizable", "description": "Shown when the value fails general type validation" }, "minimum": { "$ref": "#/$defs/localizable", "description": "Shown when the value is below `minimum`" }, "maximum": { "$ref": "#/$defs/localizable", "description": "Shown when the value exceeds `maximum`" }, "exclusiveMinimum": { "$ref": "#/$defs/localizable", "description": "Shown when the value is not strictly above `exclusiveMinimum`" }, "exclusiveMaximum": { "$ref": "#/$defs/localizable", "description": "Shown when the value is not strictly below `exclusiveMaximum`" }, "multipleOf": { "$ref": "#/$defs/localizable", "description": "Shown when the value is not a multiple of `multipleOf`" }, "enum": { "$ref": "#/$defs/localizable", "description": "Shown when the value is not in the `enum` list" }, "const": { "$ref": "#/$defs/localizable", "description": "Shown when the value does not equal `const`" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "booleanValidator": { "type": "object", "properties": { "type": { "const": "boolean" }, "required": { "type": "boolean", "description": "Makes the field mandatory β validation fails if the value is absent or false (e.g. an unchecked required checkbox)" }, "const": { "description": "The value must equal exactly this boolean (e.g. `true` to require acceptance)" }, "messages": { "type": "object", "description": "Custom error messages that override the default text for each constraint violation", "properties": { "invalid": { "$ref": "#/$defs/localizable", "description": "Shown when the value fails general type validation" }, "const": { "$ref": "#/$defs/localizable", "description": "Shown when the value does not equal `const`" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "arrayValidator": { "type": "object", "properties": { "type": { "const": "array" }, "required": { "type": "boolean", "description": "Makes the field mandatory β validation fails if the array is empty or absent" }, "minItems": { "type": "number", "description": "The array must contain at least this many items" }, "maxItems": { "type": "number", "description": "The array must contain no more than this many items" }, "uniqueItems": { "type": "boolean", "description": "When true, all items in the array must be distinct values" }, "messages": { "type": "object", "description": "Custom error messages that override the default text for each constraint violation", "properties": { "invalid": { "$ref": "#/$defs/localizable", "description": "Shown when the value fails general type validation" }, "required": { "$ref": "#/$defs/localizable", "description": "Shown when a required array is empty or absent" }, "minItems": { "$ref": "#/$defs/localizable", "description": "Shown when the array has fewer items than `minItems`" }, "maxItems": { "$ref": "#/$defs/localizable", "description": "Shown when the array has more items than `maxItems`" } }, "additionalProperties": false } }, "required": ["type"], "additionalProperties": false }, "customValidator": { "type": "object", "description": "A custom validator that delegates validation to application code. The registered validator function receives these constraint keys and must return a Standard Schema V1-compliant schema object (https://standardschema.dev). Add arbitrary constraint keys alongside `type` β they are passed as-is to the registered validator function.", "properties": { "type": { "const": "custom" }, "required": { "type": "boolean", "description": "Makes the field mandatory β evaluation is still delegated to the custom validator" } }, "required": ["type"], "additionalProperties": true }, "validator": { "description": "Validation rules for this field. The `type` discriminant selects the validator: `string`, `number`, `integer`, `boolean`, `array`, or `custom`.", "oneOf": [{ "$ref": "#/$defs/stringValidator" }, { "$ref": "#/$defs/numberValidator" }, { "$ref": "#/$defs/booleanValidator" }, { "$ref": "#/$defs/arrayValidator" }, { "$ref": "#/$defs/customValidator" }] } };
|
|
405
411
|
const validatorsSchema = {
|
|
406
412
|
$schema: $schema$r,
|
|
407
413
|
$id: $id$r,
|
|
@@ -412,9 +418,9 @@ const $schema$q = "https://json-schema.org/draft/2020-12/schema";
|
|
|
412
418
|
const $id$q = "https://golemui.com/schemas/components/accordion.schema.json";
|
|
413
419
|
const title$q = "Accordion Widget";
|
|
414
420
|
const type$q = "object";
|
|
415
|
-
const $defs$q = { "singleOpenProp": { "type": "boolean" }, "defaultOpenProp": { "type": "object", "additionalProperties": { "type": "boolean" }, "minProperties": 1 }, "renderModeProp": { "enum": ["all", "activeOnly"] }, "sectionsProp": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "label": { "$ref": "../common.schema.json#/$defs/localizable" }, "uid": { "type": "string" } }, "required": ["label", "uid"], "additionalProperties": false } } };
|
|
421
|
+
const $defs$q = { "singleOpenProp": { "type": "boolean", "description": "When true, opening one section automatically closes all others. Defaults to false." }, "defaultOpenProp": { "type": "object", "description": "Map of section UIDs to booleans indicating which sections are open on initial render", "additionalProperties": { "type": "boolean" }, "minProperties": 1 }, "renderModeProp": { "description": "Controls when section content is rendered: `all` keeps all sections in the DOM, `activeOnly` unmounts inactive ones", "enum": ["all", "activeOnly"] }, "sectionsProp": { "type": "array", "description": "Ordered list of section definitions. Each section's `uid` must match the widget UIDs used in `children`.", "minItems": 1, "items": { "type": "object", "properties": { "label": { "$ref": "../common.schema.json#/$defs/localizable" }, "uid": { "type": "string", "description": "Unique identifier linking this section definition to its widgets in `children`" } }, "required": ["label", "uid"], "additionalProperties": false } } };
|
|
416
422
|
const allOf$q = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
417
|
-
const properties$q = { "kind": { "const": "layout" }, "type": { "const": "accordion" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "children": { "type": "array", "minItems": 1, "items": { "$ref": "../form.schema.json#/$defs/formWidget" } }, "props": { "type": "object", "properties": { "singleOpen": { "$ref": "#/$defs/singleOpenProp" }, "defaultOpen": { "$ref": "#/$defs/defaultOpenProp" }, "renderMode": { "$ref": "#/$defs/renderModeProp" }, "sections": { "$ref": "#/$defs/sectionsProp" } }, "required": ["sections"], "patternProperties": { "^singleOpen\\.[^.]+$": { "$ref": "#/$defs/singleOpenProp" }, "^defaultOpen\\.[^.]+$": { "$ref": "#/$defs/defaultOpenProp" }, "^renderMode\\.[^.]+$": { "$ref": "#/$defs/renderModeProp" }, "^sections\\.[^.]+$": { "$ref": "#/$defs/sectionsProp" } }, "additionalProperties": false } };
|
|
423
|
+
const properties$q = { "kind": { "const": "layout" }, "type": { "const": "accordion" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "children": { "type": "array", "description": "Flat list of widgets belonging to all sections. Each widget must have a `uid` matching one of the section UIDs in `props.sections`.", "minItems": 1, "items": { "$ref": "../form.schema.json#/$defs/formWidget" } }, "props": { "type": "object", "properties": { "singleOpen": { "$ref": "#/$defs/singleOpenProp" }, "defaultOpen": { "$ref": "#/$defs/defaultOpenProp" }, "renderMode": { "$ref": "#/$defs/renderModeProp" }, "sections": { "$ref": "#/$defs/sectionsProp" } }, "required": ["sections"], "patternProperties": { "^singleOpen\\.[^.]+$": { "$ref": "#/$defs/singleOpenProp" }, "^defaultOpen\\.[^.]+$": { "$ref": "#/$defs/defaultOpenProp" }, "^renderMode\\.[^.]+$": { "$ref": "#/$defs/renderModeProp" }, "^sections\\.[^.]+$": { "$ref": "#/$defs/sectionsProp" } }, "additionalProperties": false } };
|
|
418
424
|
const required$q = ["kind", "type", "children", "props"];
|
|
419
425
|
const unevaluatedProperties$q = false;
|
|
420
426
|
const accordion = {
|
|
@@ -474,7 +480,7 @@ const $schema$n = "https://json-schema.org/draft/2020-12/schema";
|
|
|
474
480
|
const $id$n = "https://golemui.com/schemas/components/calendar.schema.json";
|
|
475
481
|
const title$n = "Calendar Widget";
|
|
476
482
|
const type$n = "object";
|
|
477
|
-
const $defs$n = { "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "prevMonthIconProp": { "type": "string" }, "nextMonthIconProp": { "type": "string" }, "prevMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "nextMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "dayFormatProp": { "enum": ["numeric", "2-digit"] }, "weekdayFormatProp": { "enum": ["short", "long", "narrow"] }, "monthFormatProp": { "enum": ["numeric", "2-digit", "long", "short", "narrow"] }, "minDateProp": { "type": "string" }, "maxDateProp": { "type": "string" }, "numberOfMonthsProp": { "type": "number" }, "disabledRangesProp": { "type": "array", "items": { "$ref": "../common.schema.json#/$defs/dateRange" } } };
|
|
483
|
+
const $defs$n = { "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "prevMonthIconProp": { "type": "string" }, "nextMonthIconProp": { "type": "string" }, "prevMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "nextMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "dayFormatProp": { "description": "Formatting of day numbers in the calendar grid (`numeric` = 1β31, `2-digit` = 01β31)", "enum": ["numeric", "2-digit"] }, "weekdayFormatProp": { "description": "Formatting of weekday column headers (`short` = Mon, `long` = Monday, `narrow` = M)", "enum": ["short", "long", "narrow"] }, "monthFormatProp": { "description": "Formatting of the month heading (`long` = January, `short` = Jan, `narrow` = J, `numeric` = 1, `2-digit` = 01)", "enum": ["numeric", "2-digit", "long", "short", "narrow"] }, "minDateProp": { "type": "string", "description": "ISO-8601 date string for the earliest selectable date (inclusive)" }, "maxDateProp": { "type": "string", "description": "ISO-8601 date string for the latest selectable date (inclusive)" }, "numberOfMonthsProp": { "type": "number", "description": "Number of month panels to display side by side. Defaults to 1." }, "disabledRangesProp": { "type": "array", "description": "Date ranges that are shown but cannot be selected", "items": { "$ref": "../common.schema.json#/$defs/dateRange" } } };
|
|
478
484
|
const allOf$n = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
479
485
|
const properties$n = { "kind": { "const": "input" }, "type": { "const": "calendar" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "disabled": { "$ref": "#/$defs/disabledDef" }, "readonly": { "$ref": "#/$defs/readonlyDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "props": { "type": "object", "properties": { "hint": { "$ref": "#/$defs/hintProp" }, "prevMonthIcon": { "$ref": "#/$defs/prevMonthIconProp" }, "nextMonthIcon": { "$ref": "#/$defs/nextMonthIconProp" }, "prevMonthAriaLabel": { "$ref": "#/$defs/prevMonthAriaLabelProp" }, "nextMonthAriaLabel": { "$ref": "#/$defs/nextMonthAriaLabelProp" }, "dayFormat": { "$ref": "#/$defs/dayFormatProp" }, "weekdayFormat": { "$ref": "#/$defs/weekdayFormatProp" }, "monthFormat": { "$ref": "#/$defs/monthFormatProp" }, "minDate": { "$ref": "#/$defs/minDateProp" }, "maxDate": { "$ref": "#/$defs/maxDateProp" }, "numberOfMonths": { "$ref": "#/$defs/numberOfMonthsProp" }, "disabledRanges": { "$ref": "#/$defs/disabledRangesProp" } }, "patternProperties": { "^hint\\.[^.]+$": { "$ref": "#/$defs/hintProp" }, "^prevMonthIcon\\.[^.]+$": { "$ref": "#/$defs/prevMonthIconProp" }, "^nextMonthIcon\\.[^.]+$": { "$ref": "#/$defs/nextMonthIconProp" }, "^prevMonthAriaLabel\\.[^.]+$": { "$ref": "#/$defs/prevMonthAriaLabelProp" }, "^nextMonthAriaLabel\\.[^.]+$": { "$ref": "#/$defs/nextMonthAriaLabelProp" }, "^dayFormat\\.[^.]+$": { "$ref": "#/$defs/dayFormatProp" }, "^weekdayFormat\\.[^.]+$": { "$ref": "#/$defs/weekdayFormatProp" }, "^monthFormat\\.[^.]+$": { "$ref": "#/$defs/monthFormatProp" }, "^minDate\\.[^.]+$": { "$ref": "#/$defs/minDateProp" }, "^maxDate\\.[^.]+$": { "$ref": "#/$defs/maxDateProp" }, "^numberOfMonths\\.[^.]+$": { "$ref": "#/$defs/numberOfMonthsProp" }, "^disabledRanges\\.[^.]+$": { "$ref": "#/$defs/disabledRangesProp" } }, "additionalProperties": false } };
|
|
480
486
|
const patternProperties$j = { "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^disabled\\.[^.]+$": { "$ref": "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { "$ref": "#/$defs/readonlyDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -584,7 +590,7 @@ const $schema$i = "https://json-schema.org/draft/2020-12/schema";
|
|
|
584
590
|
const $id$i = "https://golemui.com/schemas/components/dropdown.schema.json";
|
|
585
591
|
const title$i = "Dropdown Widget";
|
|
586
592
|
const type$i = "object";
|
|
587
|
-
const $defs$i = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "placeholderProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "itemsProp": { "type": "array", "description": "Items are either primitive values (string/number) or arbitrary record objects (paired with `labelField`/`valueField`/`itemRenderer` for custom rendering). Note: widget templates inside items are only supported on `repeater`, not here.", "items": { "type": ["string", "number", "object"] } }, "labelFieldProp": { "type": "string" }, "valueFieldProp": { "type": "string" }, "searchFieldsProp": { "type": "array", "items": { "type": "string" } }, "heightProp": { "type": "number" }, "itemHeightProp": { "type": "number" }, "itemRendererProp": { "type": "string" }, "inputDebounceProp": { "type": "number" }, "autocompleteProp": { "type": "string" } };
|
|
593
|
+
const $defs$i = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "placeholderProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "itemsProp": { "type": "array", "description": "Items are either primitive values (string/number) or arbitrary record objects (paired with `labelField`/`valueField`/`itemRenderer` for custom rendering). Note: widget templates inside items are only supported on `repeater`, not here.", "items": { "type": ["string", "number", "object"] } }, "labelFieldProp": { "type": "string", "description": 'Object property used as the display label when items are objects (e.g. `"name"` reads `item.name`)' }, "valueFieldProp": { "type": "string", "description": 'Object property used as the stored value when items are objects (e.g. `"id"` stores `item.id`)' }, "searchFieldsProp": { "type": "array", "description": "Object properties searched when the user types in the filter box. Defaults to `labelField` when omitted.", "items": { "type": "string" } }, "heightProp": { "type": "number", "description": "Fixed pixel height of the dropdown list panel (enables virtual scrolling for large lists)" }, "itemHeightProp": { "type": "number", "description": "Fixed pixel height of each item row, required for virtual scrolling when `height` is set" }, "itemRendererProp": { "type": "string", "description": "Name of a registered custom item renderer component used to display each option" }, "inputDebounceProp": { "type": "number", "description": "Milliseconds to debounce the filter input before firing the `on.filter` event (default: 0)" }, "autocompleteProp": { "type": "string", "description": "HTML `autocomplete` attribute applied to the underlying input element" } };
|
|
588
594
|
const allOf$i = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
589
595
|
const properties$i = { "kind": { "const": "input" }, "type": { "const": "dropdown" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "disabled": { "$ref": "#/$defs/disabledDef" }, "readonly": { "$ref": "#/$defs/readonlyDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "props": { "type": "object", "required": ["items"], "properties": { "placeholder": { "$ref": "#/$defs/placeholderProp" }, "hint": { "$ref": "#/$defs/hintProp" }, "items": { "$ref": "#/$defs/itemsProp" }, "labelField": { "$ref": "#/$defs/labelFieldProp" }, "valueField": { "$ref": "#/$defs/valueFieldProp" }, "searchFields": { "$ref": "#/$defs/searchFieldsProp" }, "height": { "$ref": "#/$defs/heightProp" }, "itemHeight": { "$ref": "#/$defs/itemHeightProp" }, "itemRenderer": { "$ref": "#/$defs/itemRendererProp" }, "inputDebounce": { "$ref": "#/$defs/inputDebounceProp" }, "autocomplete": { "$ref": "#/$defs/autocompleteProp" } }, "patternProperties": { "^placeholder\\.[^.]+$": { "$ref": "#/$defs/placeholderProp" }, "^hint\\.[^.]+$": { "$ref": "#/$defs/hintProp" }, "^items\\.[^.]+$": { "$ref": "#/$defs/itemsProp" }, "^labelField\\.[^.]+$": { "$ref": "#/$defs/labelFieldProp" }, "^valueField\\.[^.]+$": { "$ref": "#/$defs/valueFieldProp" }, "^searchFields\\.[^.]+$": { "$ref": "#/$defs/searchFieldsProp" }, "^height\\.[^.]+$": { "$ref": "#/$defs/heightProp" }, "^itemHeight\\.[^.]+$": { "$ref": "#/$defs/itemHeightProp" }, "^itemRenderer\\.[^.]+$": { "$ref": "#/$defs/itemRendererProp" }, "^inputDebounce\\.[^.]+$": { "$ref": "#/$defs/inputDebounceProp" }, "^autocomplete\\.[^.]+$": { "$ref": "#/$defs/autocompleteProp" } }, "additionalProperties": false } };
|
|
590
596
|
const patternProperties$e = { "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^disabled\\.[^.]+$": { "$ref": "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { "$ref": "#/$defs/readonlyDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -754,7 +760,7 @@ const $schema$a = "https://json-schema.org/draft/2020-12/schema";
|
|
|
754
760
|
const $id$a = "https://golemui.com/schemas/components/radiogroup.schema.json";
|
|
755
761
|
const title$a = "RadioGroup Widget";
|
|
756
762
|
const type$a = "object";
|
|
757
|
-
const $defs$a = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "optionsProp": { "type": "array", "description": "Options may be primitive values (string/number), structured `{ label, value }` records, or arbitrary objects (paired with `labelField`/`valueField` for custom rendering).", "items": { "type": ["string", "number", "object"] } }, "labelFieldProp": { "type": "string" }, "valueFieldProp": { "type": "string" }, "directionProp": { "enum": ["row", "column"], "default": "column" } };
|
|
763
|
+
const $defs$a = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "optionsProp": { "type": "array", "description": "Options may be primitive values (string/number), structured `{ label, value }` records, or arbitrary objects (paired with `labelField`/`valueField` for custom rendering).", "items": { "type": ["string", "number", "object"] } }, "labelFieldProp": { "type": "string", "description": 'Object property used as the display label when options are objects (e.g. `"name"` reads `item.name`)' }, "valueFieldProp": { "type": "string", "description": 'Object property used as the stored value when options are objects (e.g. `"id"` stores `item.id`)' }, "directionProp": { "description": "Layout direction for the radio buttons (`row` = horizontal, `column` = vertical). Defaults to `column`.", "enum": ["row", "column"], "default": "column" } };
|
|
758
764
|
const allOf$a = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
759
765
|
const properties$a = { "kind": { "const": "input" }, "type": { "const": "radiogroup" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "disabled": { "$ref": "#/$defs/disabledDef" }, "readonly": { "$ref": "#/$defs/readonlyDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "props": { "type": "object", "properties": { "hint": { "$ref": "#/$defs/hintProp" }, "options": { "$ref": "#/$defs/optionsProp" }, "labelField": { "$ref": "#/$defs/labelFieldProp" }, "valueField": { "$ref": "#/$defs/valueFieldProp" }, "direction": { "$ref": "#/$defs/directionProp" } }, "patternProperties": { "^hint\\.[^.]+$": { "$ref": "#/$defs/hintProp" }, "^options\\.[^.]+$": { "$ref": "#/$defs/optionsProp" }, "^labelField\\.[^.]+$": { "$ref": "#/$defs/labelFieldProp" }, "^valueField\\.[^.]+$": { "$ref": "#/$defs/valueFieldProp" }, "^direction\\.[^.]+$": { "$ref": "#/$defs/directionProp" } }, "additionalProperties": false } };
|
|
760
766
|
const patternProperties$9 = { "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^disabled\\.[^.]+$": { "$ref": "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { "$ref": "#/$defs/readonlyDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -776,7 +782,7 @@ const $schema$9 = "https://json-schema.org/draft/2020-12/schema";
|
|
|
776
782
|
const $id$9 = "https://golemui.com/schemas/components/rangecalendar.schema.json";
|
|
777
783
|
const title$9 = "RangeCalendar Widget";
|
|
778
784
|
const type$9 = "object";
|
|
779
|
-
const $defs$9 = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "prevMonthIconProp": { "type": "string" }, "nextMonthIconProp": { "type": "string" }, "prevMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "nextMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "removePillAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "dayFormatProp": { "enum": ["numeric", "2-digit"] }, "weekdayFormatProp": { "enum": ["short", "long", "narrow"] }, "monthFormatProp": { "enum": ["numeric", "2-digit", "long", "short", "narrow"] }, "minDateProp": { "type": "string" }, "maxDateProp": { "type": "string" }, "numberOfMonthsProp": { "type": "number" }, "disabledRangesProp": { "type": "array", "items": { "$ref": "../common.schema.json#/$defs/dateRange" } } };
|
|
785
|
+
const $defs$9 = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "prevMonthIconProp": { "type": "string" }, "nextMonthIconProp": { "type": "string" }, "prevMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "nextMonthAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "removePillAriaLabelProp": { "description": "Accessible label for the button that clears the selected range pill", "$ref": "../common.schema.json#/$defs/localizable" }, "dayFormatProp": { "description": "Formatting of day numbers in the calendar grid (`numeric` = 1β31, `2-digit` = 01β31)", "enum": ["numeric", "2-digit"] }, "weekdayFormatProp": { "description": "Formatting of weekday column headers (`short` = Mon, `long` = Monday, `narrow` = M)", "enum": ["short", "long", "narrow"] }, "monthFormatProp": { "description": "Formatting of the month heading (`long` = January, `short` = Jan, `narrow` = J, `numeric` = 1, `2-digit` = 01)", "enum": ["numeric", "2-digit", "long", "short", "narrow"] }, "minDateProp": { "type": "string", "description": "ISO-8601 date string for the earliest selectable date (inclusive)" }, "maxDateProp": { "type": "string", "description": "ISO-8601 date string for the latest selectable date (inclusive)" }, "numberOfMonthsProp": { "type": "number", "description": "Number of month panels to display side by side. Defaults to 1." }, "disabledRangesProp": { "type": "array", "description": "Date ranges that are shown but cannot be selected", "items": { "$ref": "../common.schema.json#/$defs/dateRange" } } };
|
|
780
786
|
const allOf$9 = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
781
787
|
const properties$9 = { "kind": { "const": "input" }, "type": { "const": "rangeCalendar" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "disabled": { "$ref": "#/$defs/disabledDef" }, "readonly": { "$ref": "#/$defs/readonlyDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "props": { "type": "object", "properties": { "hint": { "$ref": "#/$defs/hintProp" }, "prevMonthIcon": { "$ref": "#/$defs/prevMonthIconProp" }, "nextMonthIcon": { "$ref": "#/$defs/nextMonthIconProp" }, "prevMonthAriaLabel": { "$ref": "#/$defs/prevMonthAriaLabelProp" }, "nextMonthAriaLabel": { "$ref": "#/$defs/nextMonthAriaLabelProp" }, "removePillAriaLabel": { "$ref": "#/$defs/removePillAriaLabelProp" }, "dayFormat": { "$ref": "#/$defs/dayFormatProp" }, "weekdayFormat": { "$ref": "#/$defs/weekdayFormatProp" }, "monthFormat": { "$ref": "#/$defs/monthFormatProp" }, "minDate": { "$ref": "#/$defs/minDateProp" }, "maxDate": { "$ref": "#/$defs/maxDateProp" }, "numberOfMonths": { "$ref": "#/$defs/numberOfMonthsProp" }, "disabledRanges": { "$ref": "#/$defs/disabledRangesProp" } }, "patternProperties": { "^hint\\.[^.]+$": { "$ref": "#/$defs/hintProp" }, "^prevMonthIcon\\.[^.]+$": { "$ref": "#/$defs/prevMonthIconProp" }, "^nextMonthIcon\\.[^.]+$": { "$ref": "#/$defs/nextMonthIconProp" }, "^prevMonthAriaLabel\\.[^.]+$": { "$ref": "#/$defs/prevMonthAriaLabelProp" }, "^nextMonthAriaLabel\\.[^.]+$": { "$ref": "#/$defs/nextMonthAriaLabelProp" }, "^removePillAriaLabel\\.[^.]+$": { "$ref": "#/$defs/removePillAriaLabelProp" }, "^dayFormat\\.[^.]+$": { "$ref": "#/$defs/dayFormatProp" }, "^weekdayFormat\\.[^.]+$": { "$ref": "#/$defs/weekdayFormatProp" }, "^monthFormat\\.[^.]+$": { "$ref": "#/$defs/monthFormatProp" }, "^minDate\\.[^.]+$": { "$ref": "#/$defs/minDateProp" }, "^maxDate\\.[^.]+$": { "$ref": "#/$defs/maxDateProp" }, "^numberOfMonths\\.[^.]+$": { "$ref": "#/$defs/numberOfMonthsProp" }, "^disabledRanges\\.[^.]+$": { "$ref": "#/$defs/disabledRangesProp" } }, "additionalProperties": false } };
|
|
782
788
|
const patternProperties$8 = { "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^disabled\\.[^.]+$": { "$ref": "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { "$ref": "#/$defs/readonlyDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -842,7 +848,7 @@ const $schema$6 = "https://json-schema.org/draft/2020-12/schema";
|
|
|
842
848
|
const $id$6 = "https://golemui.com/schemas/components/repeater.schema.json";
|
|
843
849
|
const title$6 = "Repeater Widget";
|
|
844
850
|
const type$6 = "object";
|
|
845
|
-
const $defs$6 = { "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "addLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "removeLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "limitProp": { "type": "number" }, "templateProp": { "$ref": "../layout-widget.schema.json" }, "titleProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "addButtonIconProp": { "type": "string" }, "removeButtonIconProp": { "type": "string" } };
|
|
851
|
+
const $defs$6 = { "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "addLabelProp": { "description": "Label for the button that adds a new repeated item", "$ref": "../common.schema.json#/$defs/localizable" }, "removeLabelProp": { "description": "Label for the button that removes an existing repeated item", "$ref": "../common.schema.json#/$defs/localizable" }, "limitProp": { "type": "number", "description": "Maximum number of items the user can add. No limit when omitted." }, "templateProp": { "description": "Widget tree used as the template for each repeated item. Paths inside are resolved relative to the item's index in the array.", "$ref": "../layout-widget.schema.json" }, "titleProp": { "description": "Title displayed in the header of each item panel (supports reactive expressions referencing the item's index via `$index`)", "$ref": "../common.schema.json#/$defs/localizable" }, "addButtonIconProp": { "type": "string", "description": "Icon name or CSS class for the add-item button icon" }, "removeButtonIconProp": { "type": "string", "description": "Icon name or CSS class for the remove-item button icon" } };
|
|
846
852
|
const allOf$6 = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
847
853
|
const properties$6 = { "kind": { "const": "input" }, "type": { "const": "repeater" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "props": { "type": "object", "properties": { "addLabel": { "$ref": "#/$defs/addLabelProp" }, "removeLabel": { "$ref": "#/$defs/removeLabelProp" }, "limit": { "$ref": "#/$defs/limitProp" }, "template": { "$ref": "#/$defs/templateProp" }, "title": { "$ref": "#/$defs/titleProp" }, "addButtonIcon": { "$ref": "#/$defs/addButtonIconProp" }, "removeButtonIcon": { "$ref": "#/$defs/removeButtonIconProp" } }, "required": ["template"], "patternProperties": { "^addLabel\\.[^.]+$": { "$ref": "#/$defs/addLabelProp" }, "^removeLabel\\.[^.]+$": { "$ref": "#/$defs/removeLabelProp" }, "^limit\\.[^.]+$": { "$ref": "#/$defs/limitProp" }, "^template\\.[^.]+$": { "$ref": "#/$defs/templateProp" }, "^title\\.[^.]+$": { "$ref": "#/$defs/titleProp" }, "^addButtonIcon\\.[^.]+$": { "$ref": "#/$defs/addButtonIconProp" }, "^removeButtonIcon\\.[^.]+$": { "$ref": "#/$defs/removeButtonIconProp" } }, "additionalProperties": false } };
|
|
848
854
|
const patternProperties$5 = { "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -864,7 +870,7 @@ const $schema$5 = "https://json-schema.org/draft/2020-12/schema";
|
|
|
864
870
|
const $id$5 = "https://golemui.com/schemas/components/select.schema.json";
|
|
865
871
|
const title$5 = "Select Widget";
|
|
866
872
|
const type$5 = "object";
|
|
867
|
-
const $defs$5 = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "iconProp": { "type": "string" }, "optionsProp": { "type": "array", "description": "Options may be primitive values (string/number), structured `{ label, value }` records, or arbitrary objects (paired with `labelField`/`valueField` for custom rendering).", "items": { "type": ["string", "number", "object"] } }, "placeholderProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "labelFieldProp": { "type": "string" }, "valueFieldProp": { "type": "string" }, "autocompleteProp": { "type": "string" } };
|
|
873
|
+
const $defs$5 = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "iconProp": { "type": "string" }, "optionsProp": { "type": "array", "description": "Options may be primitive values (string/number), structured `{ label, value }` records, or arbitrary objects (paired with `labelField`/`valueField` for custom rendering).", "items": { "type": ["string", "number", "object"] } }, "placeholderProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "labelFieldProp": { "type": "string", "description": 'Object property used as the display label when options are objects (e.g. `"name"` reads `item.name`)' }, "valueFieldProp": { "type": "string", "description": 'Object property used as the stored value when options are objects (e.g. `"id"` stores `item.id`)' }, "autocompleteProp": { "type": "string", "description": "HTML `autocomplete` attribute applied to the underlying input element" } };
|
|
868
874
|
const allOf$5 = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
869
875
|
const properties$5 = { "kind": { "const": "input" }, "type": { "const": "select" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "disabled": { "$ref": "#/$defs/disabledDef" }, "readonly": { "$ref": "#/$defs/readonlyDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "props": { "type": "object", "properties": { "hint": { "$ref": "#/$defs/hintProp" }, "icon": { "$ref": "#/$defs/iconProp" }, "options": { "$ref": "#/$defs/optionsProp" }, "placeholder": { "$ref": "#/$defs/placeholderProp" }, "labelField": { "$ref": "#/$defs/labelFieldProp" }, "valueField": { "$ref": "#/$defs/valueFieldProp" }, "autocomplete": { "$ref": "#/$defs/autocompleteProp" } }, "patternProperties": { "^hint\\.[^.]+$": { "$ref": "#/$defs/hintProp" }, "^icon\\.[^.]+$": { "$ref": "#/$defs/iconProp" }, "^options\\.[^.]+$": { "$ref": "#/$defs/optionsProp" }, "^placeholder\\.[^.]+$": { "$ref": "#/$defs/placeholderProp" }, "^labelField\\.[^.]+$": { "$ref": "#/$defs/labelFieldProp" }, "^valueField\\.[^.]+$": { "$ref": "#/$defs/valueFieldProp" }, "^autocomplete\\.[^.]+$": { "$ref": "#/$defs/autocompleteProp" } }, "additionalProperties": false } };
|
|
870
876
|
const patternProperties$4 = { "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^disabled\\.[^.]+$": { "$ref": "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { "$ref": "#/$defs/readonlyDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -886,9 +892,9 @@ const $schema$4 = "https://json-schema.org/draft/2020-12/schema";
|
|
|
886
892
|
const $id$4 = "https://golemui.com/schemas/components/tabs.schema.json";
|
|
887
893
|
const title$4 = "Tabs Widget";
|
|
888
894
|
const type$4 = "object";
|
|
889
|
-
const $defs$4 = { "defaultOpenProp": { "type": "string" }, "renderModeProp": { "enum": ["all", "activeOnly"] }, "tabsProp": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "label": { "$ref": "../common.schema.json#/$defs/localizable" }, "uid": { "type": "string" } }, "required": ["label", "uid"], "additionalProperties": false } } };
|
|
895
|
+
const $defs$4 = { "defaultOpenProp": { "type": "string", "description": "UID of the tab that is active on initial render. Defaults to the first tab." }, "renderModeProp": { "description": "Controls when tab content is rendered: `all` keeps all tabs in the DOM, `activeOnly` unmounts inactive ones", "enum": ["all", "activeOnly"] }, "tabsProp": { "type": "array", "description": "Ordered list of tab definitions. Each tab's `uid` must match the widget UIDs used in `children`.", "minItems": 1, "items": { "type": "object", "properties": { "label": { "$ref": "../common.schema.json#/$defs/localizable" }, "uid": { "type": "string", "description": "Unique identifier linking this tab definition to its widgets in `children`" } }, "required": ["label", "uid"], "additionalProperties": false } } };
|
|
890
896
|
const allOf$4 = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
891
|
-
const properties$4 = { "kind": { "const": "layout" }, "type": { "const": "tabs" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "children": { "type": "array", "minItems": 1, "items": { "$ref": "../form.schema.json#/$defs/formWidget" } }, "props": { "type": "object", "properties": { "defaultOpen": { "$ref": "#/$defs/defaultOpenProp" }, "renderMode": { "$ref": "#/$defs/renderModeProp" }, "tabs": { "$ref": "#/$defs/tabsProp" } }, "required": ["tabs"], "patternProperties": { "^defaultOpen\\.[^.]+$": { "$ref": "#/$defs/defaultOpenProp" }, "^renderMode\\.[^.]+$": { "$ref": "#/$defs/renderModeProp" }, "^tabs\\.[^.]+$": { "$ref": "#/$defs/tabsProp" } }, "additionalProperties": false } };
|
|
897
|
+
const properties$4 = { "kind": { "const": "layout" }, "type": { "const": "tabs" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "children": { "type": "array", "description": "Flat list of widgets belonging to all tabs. Each widget must have a `uid` matching one of the tab UIDs in `props.tabs`.", "minItems": 1, "items": { "$ref": "../form.schema.json#/$defs/formWidget" } }, "props": { "type": "object", "properties": { "defaultOpen": { "$ref": "#/$defs/defaultOpenProp" }, "renderMode": { "$ref": "#/$defs/renderModeProp" }, "tabs": { "$ref": "#/$defs/tabsProp" } }, "required": ["tabs"], "patternProperties": { "^defaultOpen\\.[^.]+$": { "$ref": "#/$defs/defaultOpenProp" }, "^renderMode\\.[^.]+$": { "$ref": "#/$defs/renderModeProp" }, "^tabs\\.[^.]+$": { "$ref": "#/$defs/tabsProp" } }, "additionalProperties": false } };
|
|
892
898
|
const required$4 = ["kind", "type", "children", "props"];
|
|
893
899
|
const unevaluatedProperties$4 = false;
|
|
894
900
|
const tabs = {
|
|
@@ -906,7 +912,7 @@ const $schema$3 = "https://json-schema.org/draft/2020-12/schema";
|
|
|
906
912
|
const $id$3 = "https://golemui.com/schemas/components/tags.schema.json";
|
|
907
913
|
const title$3 = "Tags Widget";
|
|
908
914
|
const type$3 = "object";
|
|
909
|
-
const $defs$3 = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "placeholderProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "iconProp": { "type": "string" }, "separatorsProp": { "type": "array", "items": { "type": "string" } }, "allowDuplicatesProp": { "type": "boolean" }, "trimProp": { "type": "boolean" }, "limitProp": { "type": "number" }, "removeAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "removeIconProp": { "type": "string" } };
|
|
915
|
+
const $defs$3 = { "disabledDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "readonlyDef": { "$ref": "../common.schema.json#/$defs/boolOrWhen" }, "labelDef": { "$ref": "../common.schema.json#/$defs/localizable" }, "hintProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "placeholderProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "iconProp": { "type": "string" }, "separatorsProp": { "type": "array", "description": 'Characters or strings that trigger a new tag when typed (e.g. `[",", " "]`). Defaults to Enter key only.', "items": { "type": "string" } }, "allowDuplicatesProp": { "type": "boolean", "description": "When true, the same tag value may be added more than once. Defaults to false." }, "trimProp": { "type": "boolean", "description": "When true, whitespace is trimmed from each tag before it is added. Defaults to true." }, "limitProp": { "type": "number", "description": "Maximum number of tags the user can add. No limit when omitted." }, "removeAriaLabelProp": { "$ref": "../common.schema.json#/$defs/localizable" }, "removeIconProp": { "type": "string" } };
|
|
910
916
|
const allOf$3 = [{ "$ref": "../common.schema.json#/$defs/baseWidget" }];
|
|
911
917
|
const properties$3 = { "kind": { "const": "input" }, "type": { "const": "tags" }, "path": { "$ref": "../common.schema.json#/$defs/dotPath" }, "label": { "$ref": "#/$defs/labelDef" }, "disabled": { "$ref": "#/$defs/disabledDef" }, "readonly": { "$ref": "#/$defs/readonlyDef" }, "on": { "$ref": "../common.schema.json#/$defs/on" }, "validator": { "$ref": "../validators.schema.json#/$defs/validator" }, "defaultValue": { "type": "array", "items": { "type": "string" } }, "props": { "type": "object", "properties": { "hint": { "$ref": "#/$defs/hintProp" }, "placeholder": { "$ref": "#/$defs/placeholderProp" }, "icon": { "$ref": "#/$defs/iconProp" }, "separators": { "$ref": "#/$defs/separatorsProp" }, "allowDuplicates": { "$ref": "#/$defs/allowDuplicatesProp" }, "trim": { "$ref": "#/$defs/trimProp" }, "limit": { "$ref": "#/$defs/limitProp" }, "removeAriaLabel": { "$ref": "#/$defs/removeAriaLabelProp" }, "removeIcon": { "$ref": "#/$defs/removeIconProp" } }, "patternProperties": { "^hint\\.[^.]+$": { "$ref": "#/$defs/hintProp" }, "^placeholder\\.[^.]+$": { "$ref": "#/$defs/placeholderProp" }, "^icon\\.[^.]+$": { "$ref": "#/$defs/iconProp" }, "^separators\\.[^.]+$": { "$ref": "#/$defs/separatorsProp" }, "^allowDuplicates\\.[^.]+$": { "$ref": "#/$defs/allowDuplicatesProp" }, "^trim\\.[^.]+$": { "$ref": "#/$defs/trimProp" }, "^limit\\.[^.]+$": { "$ref": "#/$defs/limitProp" }, "^removeAriaLabel\\.[^.]+$": { "$ref": "#/$defs/removeAriaLabelProp" }, "^removeIcon\\.[^.]+$": { "$ref": "#/$defs/removeIconProp" } }, "additionalProperties": false } };
|
|
912
918
|
const patternProperties$3 = { "^disabled\\.[^.]+$": { "$ref": "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { "$ref": "#/$defs/readonlyDef" }, "^label\\.[^.]+$": { "$ref": "#/$defs/labelDef" }, "^validator\\.[^.]+$": { "$ref": "../validators.schema.json#/$defs/validator" } };
|
|
@@ -1410,13 +1416,13 @@ function dedupe(errors) {
|
|
|
1410
1416
|
}
|
|
1411
1417
|
function lintReactiveExpressions(formDefinition) {
|
|
1412
1418
|
const findings = [];
|
|
1413
|
-
walk(formDefinition, "", findings);
|
|
1419
|
+
walk$1(formDefinition, "", findings);
|
|
1414
1420
|
return findings;
|
|
1415
1421
|
}
|
|
1416
|
-
function walk(node, path, out) {
|
|
1422
|
+
function walk$1(node, path, out) {
|
|
1417
1423
|
if (node === null || typeof node !== "object") return;
|
|
1418
1424
|
if (Array.isArray(node)) {
|
|
1419
|
-
node.forEach((item, i) => walk(item, `${path}/${i}`, out));
|
|
1425
|
+
node.forEach((item, i) => walk$1(item, `${path}/${i}`, out));
|
|
1420
1426
|
return;
|
|
1421
1427
|
}
|
|
1422
1428
|
const obj = node;
|
|
@@ -1444,7 +1450,7 @@ function walk(node, path, out) {
|
|
|
1444
1450
|
}
|
|
1445
1451
|
}
|
|
1446
1452
|
if (typeof v === "object" && v !== null) {
|
|
1447
|
-
walk(v, `${path}/${k}`, out);
|
|
1453
|
+
walk$1(v, `${path}/${k}`, out);
|
|
1448
1454
|
}
|
|
1449
1455
|
}
|
|
1450
1456
|
}
|
|
@@ -1477,12 +1483,12 @@ function checkExpression(expr, path, out) {
|
|
|
1477
1483
|
});
|
|
1478
1484
|
return;
|
|
1479
1485
|
}
|
|
1480
|
-
if (!/\$form\b|\$meta\b|\$
|
|
1486
|
+
if (!/\$form\b|\$meta\b|\$formIsInvalid\b/.test(trimmed)) {
|
|
1481
1487
|
out.push({
|
|
1482
1488
|
path,
|
|
1483
1489
|
expression: expr,
|
|
1484
|
-
message: "Expression does not reference `$form`, `$meta`,
|
|
1485
|
-
suggestion: "GolemUI expressions read form data via `$form.fieldName
|
|
1490
|
+
message: "Expression does not reference `$form`, `$meta`, or `$formIsInvalid`.",
|
|
1491
|
+
suggestion: "GolemUI expressions read form data via `$form.fieldName`, form metadata via `$meta.key`, or the built-in `$formIsInvalid` boolean. Did you forget the prefix?"
|
|
1486
1492
|
});
|
|
1487
1493
|
}
|
|
1488
1494
|
if (/(?<![=!<>])=(?![=>])/.test(trimmed)) {
|
|
@@ -1509,15 +1515,15 @@ function checkExpression(expr, path, out) {
|
|
|
1509
1515
|
suggestion: "Use strict equality `===` / `!==` to avoid type coercion (e.g. `$form.x !== undefined` rather than `$form.x != null`)."
|
|
1510
1516
|
});
|
|
1511
1517
|
}
|
|
1512
|
-
if (/(?<![=!])!\s*\$(?:form|meta
|
|
1518
|
+
if (/(?<![=!])!\s*\$(?:form|meta)\b/.test(trimmed)) {
|
|
1513
1519
|
out.push({
|
|
1514
1520
|
path,
|
|
1515
1521
|
expression: expr,
|
|
1516
|
-
message: "Expression negates a `$form`/`$meta
|
|
1522
|
+
message: "Expression negates a `$form`/`$meta` reference (relies on truthy/falsy coercion).",
|
|
1517
1523
|
suggestion: 'Form data values can be `undefined`. Pick the case you actually mean and write it explicitly β `$form.x === undefined`, `$form.x === null`, `$form.x === 0`, `$form.x === ""` β instead of `!$form.x`.'
|
|
1518
1524
|
});
|
|
1519
1525
|
}
|
|
1520
|
-
const refChainRe = /\$(?:form|meta
|
|
1526
|
+
const refChainRe = /\$(?:form|meta)\b((?:\.[\w?]+)*)/g;
|
|
1521
1527
|
let chainFlagged = false;
|
|
1522
1528
|
let chainMatch;
|
|
1523
1529
|
while ((chainMatch = refChainRe.exec(trimmed)) !== null) {
|
|
@@ -1542,9 +1548,9 @@ function checkExpression(expr, path, out) {
|
|
|
1542
1548
|
chainFlagged = true;
|
|
1543
1549
|
}
|
|
1544
1550
|
}
|
|
1545
|
-
const refOnly = /^\$(?:form|meta
|
|
1546
|
-
const refBeforeBool = /\$(?:form|meta
|
|
1547
|
-
const refAfterBool = /(?:&&|\|\|)\s*\$(?:form|meta
|
|
1551
|
+
const refOnly = /^\$(?:form|meta)(?:\.[\w?]+)*$/;
|
|
1552
|
+
const refBeforeBool = /\$(?:form|meta)(?:\.[\w?]+)*\s*(?:&&|\|\||\?(?![.?]))/;
|
|
1553
|
+
const refAfterBool = /(?:&&|\|\|)\s*\$(?:form|meta)(?:\.[\w?]+)*\s*$/;
|
|
1548
1554
|
if (refOnly.test(trimmed) || refBeforeBool.test(trimmed) || refAfterBool.test(trimmed)) {
|
|
1549
1555
|
out.push({
|
|
1550
1556
|
path,
|
|
@@ -1553,7 +1559,7 @@ function checkExpression(expr, path, out) {
|
|
|
1553
1559
|
suggestion: 'Form data values can be `undefined`. Compare explicitly: `$form.x !== undefined`, `$form.x === "value"`, `$form.items?.length > 0`. For default values use nullish coalescing: `$form.x ?? defaultValue`.'
|
|
1554
1560
|
});
|
|
1555
1561
|
}
|
|
1556
|
-
const refForCmpRe = /\$(?:form|meta
|
|
1562
|
+
const refForCmpRe = /\$(?:form|meta)(?:\.[\w?]+)+/g;
|
|
1557
1563
|
let r5Flagged = false;
|
|
1558
1564
|
let cmpMatch;
|
|
1559
1565
|
while ((cmpMatch = refForCmpRe.exec(trimmed)) !== null) {
|
|
@@ -1576,11 +1582,128 @@ function checkExpression(expr, path, out) {
|
|
|
1576
1582
|
}
|
|
1577
1583
|
}
|
|
1578
1584
|
}
|
|
1585
|
+
function lintStringInterpolations(formDefinition) {
|
|
1586
|
+
const findings = [];
|
|
1587
|
+
walk(formDefinition, "", findings);
|
|
1588
|
+
return findings;
|
|
1589
|
+
}
|
|
1590
|
+
const SLOT_REGEX = /\{\{([^}]*(?:\}[^}]+)*)\}\}/g;
|
|
1591
|
+
function walk(node, path, out) {
|
|
1592
|
+
if (node === null || typeof node !== "object") {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
if (Array.isArray(node)) {
|
|
1596
|
+
node.forEach((item, i) => walk(item, `${path}/${i}`, out));
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
const obj = node;
|
|
1600
|
+
const isTranslationConfig = typeof obj["key"] === "string" && obj["params"] !== null && typeof obj["params"] === "object" && !Array.isArray(obj["params"]);
|
|
1601
|
+
if (isTranslationConfig) {
|
|
1602
|
+
const params = obj["params"];
|
|
1603
|
+
for (const [paramKey, paramValue] of Object.entries(params)) {
|
|
1604
|
+
if (typeof paramValue === "string") {
|
|
1605
|
+
checkParamExpression(paramValue, `${path}/params/${paramKey}`, out);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1610
|
+
if (key === "params" && isTranslationConfig) {
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (key === "defaultValue" || key.startsWith("defaultValue.")) {
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
const childPath = `${path}/${key}`;
|
|
1617
|
+
if (typeof value === "string") {
|
|
1618
|
+
checkTemplate(value, childPath, out);
|
|
1619
|
+
} else {
|
|
1620
|
+
walk(value, childPath, out);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function checkParamExpression(value, path, out) {
|
|
1625
|
+
if (value.includes("{{") || value.includes("}}")) {
|
|
1626
|
+
out.push({
|
|
1627
|
+
path,
|
|
1628
|
+
slot: value,
|
|
1629
|
+
message: "i18n param expression should not use `{{` / `}}` delimiters.",
|
|
1630
|
+
suggestion: 'Use a bare expression: `"$form.fieldName"` not `"{{$form.fieldName}}"`.'
|
|
1631
|
+
});
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (value.startsWith("$") && /(?<![=!<>])=(?![=>])/.test(value)) {
|
|
1635
|
+
out.push({
|
|
1636
|
+
path,
|
|
1637
|
+
slot: value,
|
|
1638
|
+
message: "i18n param expression contains a single `=` (assignment).",
|
|
1639
|
+
suggestion: "Param expressions are read-only. Did you mean `===` for equality?"
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function checkTemplate(value, path, out) {
|
|
1644
|
+
const openCount = (value.match(/\{\{/g) ?? []).length;
|
|
1645
|
+
const closeCount = (value.match(/\}\}/g) ?? []).length;
|
|
1646
|
+
if (openCount !== closeCount) {
|
|
1647
|
+
out.push({
|
|
1648
|
+
path,
|
|
1649
|
+
slot: value,
|
|
1650
|
+
message: "String template has unbalanced `{{` / `}}` delimiters.",
|
|
1651
|
+
suggestion: "Every `{{` must have a matching `}}`."
|
|
1652
|
+
});
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (openCount === 0) return;
|
|
1656
|
+
let match;
|
|
1657
|
+
SLOT_REGEX.lastIndex = 0;
|
|
1658
|
+
while ((match = SLOT_REGEX.exec(value)) !== null) {
|
|
1659
|
+
const slot = match[0];
|
|
1660
|
+
const expr = match[1];
|
|
1661
|
+
checkSlot(expr, slot, path, out);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
function checkSlot(expr, slot, path, out) {
|
|
1665
|
+
const trimmed = expr.trim();
|
|
1666
|
+
if (!trimmed) {
|
|
1667
|
+
out.push({
|
|
1668
|
+
path,
|
|
1669
|
+
slot,
|
|
1670
|
+
message: "String interpolation slot is empty.",
|
|
1671
|
+
suggestion: "Add an expression, e.g. `{{$form.fieldName}}`."
|
|
1672
|
+
});
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
if (trimmed.includes("{{")) {
|
|
1676
|
+
out.push({
|
|
1677
|
+
path,
|
|
1678
|
+
slot,
|
|
1679
|
+
message: "Interpolation slot contains nested `{{`.",
|
|
1680
|
+
suggestion: "Slots cannot be nested. Check for a copy-paste error."
|
|
1681
|
+
});
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
if (!/\$form\b|\$meta\b|\$errors\b|\$formIsInvalid\b/.test(trimmed)) {
|
|
1685
|
+
out.push({
|
|
1686
|
+
path,
|
|
1687
|
+
slot,
|
|
1688
|
+
message: "Interpolation slot does not reference `$form`, `$meta`, `$errors`, or `$formIsInvalid`.",
|
|
1689
|
+
suggestion: "GolemUI template slots read data via `$form.fieldName`, metadata via `$meta.key`, validation errors via `$errors.fieldName`, or the built-in `$formIsInvalid` boolean."
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
if (/(?<![=!<>])=(?![=>])/.test(trimmed)) {
|
|
1693
|
+
out.push({
|
|
1694
|
+
path,
|
|
1695
|
+
slot,
|
|
1696
|
+
message: "Interpolation slot contains a single `=` (assignment).",
|
|
1697
|
+
suggestion: "Template slots are read-only. Did you mean `===` for equality?"
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1579
1701
|
function validateFormDefinition(input) {
|
|
1580
1702
|
const validate = getFormValidator();
|
|
1581
1703
|
const ajvOk = validate(input.formDefinition);
|
|
1582
1704
|
const { errors, warnings } = formatAjvErrors(validate.errors, input.formDefinition);
|
|
1583
1705
|
const expressionWarnings = lintReactiveExpressions(input.formDefinition);
|
|
1706
|
+
const interpolationWarnings = lintStringInterpolations(input.formDefinition);
|
|
1584
1707
|
if (!ajvOk && errors.length === 0 && warnings.length === 0) {
|
|
1585
1708
|
errors.push({
|
|
1586
1709
|
path: "/",
|
|
@@ -1592,12 +1715,13 @@ function validateFormDefinition(input) {
|
|
|
1592
1715
|
valid: errors.length === 0,
|
|
1593
1716
|
errors,
|
|
1594
1717
|
warnings,
|
|
1595
|
-
expressionWarnings
|
|
1718
|
+
expressionWarnings,
|
|
1719
|
+
interpolationWarnings
|
|
1596
1720
|
};
|
|
1597
1721
|
}
|
|
1598
1722
|
const VALIDATE_FORM_DEFINITION_TOOL = {
|
|
1599
1723
|
name: "validate_form_definition",
|
|
1600
|
-
description: "Validate a GolemUI form definition against the bundled JSON Schemas. Use this AFTER generating or modifying a form definition to guarantee it is correct before the user pastes it into their codebase. Returns `{ valid, errors, warnings, expressionWarnings }`. Hard mistakes (typos in widget `type`, missing required props, invalid validator shapes) show up in `errors` and flip `valid` to false. Likely-custom widgets (a `type` value that isn't a built-in and isn't close to one) show up in `warnings` instead β they don't affect `valid`. Reactive expressions (`include.when`, `disabled.when`, etc.) are linted separately into `expressionWarnings`.",
|
|
1724
|
+
description: "Validate a GolemUI form definition against the bundled JSON Schemas. Use this AFTER generating or modifying a form definition to guarantee it is correct before the user pastes it into their codebase. Returns `{ valid, errors, warnings, expressionWarnings, interpolationWarnings }`. Hard mistakes (typos in widget `type`, missing required props, invalid validator shapes) show up in `errors` and flip `valid` to false. Likely-custom widgets (a `type` value that isn't a built-in and isn't close to one) show up in `warnings` instead β they don't affect `valid`. Reactive expressions (`include.when`, `disabled.when`, etc.) are linted separately into `expressionWarnings`. String interpolation templates (`{{$form.x}}`, `{{$meta.y}}`, expressions like `{{$form.count + 1}}`, etc.) in widget props, and bare expressions inside i18n `params` objects, are linted into `interpolationWarnings`.",
|
|
1601
1725
|
inputSchema: {
|
|
1602
1726
|
type: "object",
|
|
1603
1727
|
properties: {
|
|
@@ -1775,7 +1899,7 @@ function resolveLocalRef(ref, doc) {
|
|
|
1775
1899
|
}
|
|
1776
1900
|
const GENERATE_FROM_OPENAPI_TOOL = {
|
|
1777
1901
|
name: "generate_from_openapi",
|
|
1778
|
-
description: 'Generate a GolemUI form for a specific OpenAPI 3.x operation (e.g. "POST /users"). Resolves the operation\'s JSON request body, dereferences `$ref`s, then maps it to a validated
|
|
1902
|
+
description: 'Generate a GolemUI form for a specific OpenAPI 3.x operation (e.g. "POST /users"). Resolves the operation\'s JSON request body, dereferences `$ref`s, then maps it to a form definition that is validated against the GolemUI JSON Schemas before being returned, so it is guaranteed syntactically correct. Falls back to operation parameters when no request body is present. Anything the mapper cannot handle is reported in `unmapped` rather than silently dropped β use that list to surface remaining work to the user. Pass either a parsed `document` or a `documentUrl` to fetch.',
|
|
1779
1903
|
inputSchema: {
|
|
1780
1904
|
type: "object",
|
|
1781
1905
|
properties: {
|
|
@@ -1804,6 +1928,298 @@ const GENERATE_FROM_OPENAPI_TOOL = {
|
|
|
1804
1928
|
required: ["operation"]
|
|
1805
1929
|
}
|
|
1806
1930
|
};
|
|
1931
|
+
const STATES_CONCEPT = {
|
|
1932
|
+
concept: "states",
|
|
1933
|
+
summary: "States are named boolean conditions declared at the form root. Each state name maps to a reactive expression string (using `$form`, `$meta`, or `$formIsInvalid`) that the runtime evaluates continuously as the user interacts with the form. Once declared, state names can gate widget visibility (include / exclude) and swap individual widget properties per-state β a capability unique to named states that has no inline `when` equivalent.",
|
|
1934
|
+
patterns: [
|
|
1935
|
+
{
|
|
1936
|
+
name: "Declare states at the form root",
|
|
1937
|
+
description: 'Add a `"states"` object to the top-level form definition. Each key is a state name; each value is a reactive expression string. Expressions are evaluated at runtime β they have access to `$form` (all current form values), `$meta` (host-supplied metadata), and `$formIsInvalid` (built-in boolean β `true` when any field currently fails validation). State names can contain letters, numbers, hyphens, and underscores. Colons enable hierarchical composition β see the "Composed sub-states (colon notation)" pattern below.',
|
|
1938
|
+
example: {
|
|
1939
|
+
$schema: "https://golemui.com/schemas/form.schema.json",
|
|
1940
|
+
states: {
|
|
1941
|
+
termsAccepted: "$form.terms === true",
|
|
1942
|
+
hasDiscount: "$form.hasDiscountCode === true",
|
|
1943
|
+
limitReached: "$form.users?.length === 5"
|
|
1944
|
+
},
|
|
1945
|
+
form: ["/* ... widgets ... */"]
|
|
1946
|
+
}
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
name: "Composed sub-states (colon notation)",
|
|
1950
|
+
description: 'Colons in a state name denote hierarchy: `"register"`, `"register:adult"`, `"register:minor:tall"`. At runtime the form engine rewrites every child expression by ANDing the full ancestor chain in front of it. This means you write only the *incremental* condition in a child β the parent conditions are inherited automatically. A child state is active only when ALL of its ancestors are also active.',
|
|
1951
|
+
example: {
|
|
1952
|
+
// What you write in the form definition:
|
|
1953
|
+
statesAsAuthored: {
|
|
1954
|
+
register: "$form.agreeTerms === true",
|
|
1955
|
+
"register:adult": "$form.user?.age >= 18",
|
|
1956
|
+
"register:minor": "$form.user?.age < 18",
|
|
1957
|
+
"register:minor:tall": "$form.user?.height > 180"
|
|
1958
|
+
},
|
|
1959
|
+
// What the runtime actually evaluates (expandStateExpressions output):
|
|
1960
|
+
statesAtRuntime: {
|
|
1961
|
+
register: "($form.agreeTerms === true)",
|
|
1962
|
+
"register:adult": "($form.agreeTerms === true) && ($form.user?.age >= 18)",
|
|
1963
|
+
"register:minor": "($form.agreeTerms === true) && ($form.user?.age < 18)",
|
|
1964
|
+
"register:minor:tall": "($form.agreeTerms === true) && ($form.user?.age < 18) && ($form.user?.height > 180)"
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
name: "Conditional rendering with include / exclude",
|
|
1970
|
+
description: 'Use `"include": { "in": ["stateName"] }` on any widget to render it only when the named state is active. Use `"exclude": { "from": ["stateName"] }` to render it only when the state is NOT active. Both `in` and `from` are arrays β a widget can be gated on multiple states simultaneously. Prefer the named-state form (`in`/`from`) over the inline `when` expression when the same condition applies to several widgets; use `when` for one-off conditions with no reuse.',
|
|
1971
|
+
example: {
|
|
1972
|
+
$schema: "https://golemui.com/schemas/form.schema.json",
|
|
1973
|
+
states: {
|
|
1974
|
+
hasDiscount: "$form.hasDiscountCode === true"
|
|
1975
|
+
},
|
|
1976
|
+
form: [
|
|
1977
|
+
{
|
|
1978
|
+
kind: "input",
|
|
1979
|
+
type: "checkbox",
|
|
1980
|
+
path: "hasDiscountCode",
|
|
1981
|
+
label: "I have a discount code"
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
kind: "input",
|
|
1985
|
+
type: "textinput",
|
|
1986
|
+
path: "discountCode",
|
|
1987
|
+
label: "Discount code",
|
|
1988
|
+
include: { in: ["hasDiscount"] }
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
kind: "display",
|
|
1992
|
+
type: "alert",
|
|
1993
|
+
props: { text: "No discount applied." },
|
|
1994
|
+
exclude: { from: ["hasDiscount"] }
|
|
1995
|
+
}
|
|
1996
|
+
]
|
|
1997
|
+
}
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
name: "State-suffixed props",
|
|
2001
|
+
description: 'Override individual widget properties when a named state is active by appending `".<stateName>"` to the property key. The unsuffixed key holds the default value; each suffixed key supplies the override for that state. This works on root-level widget properties (`label`, `disabled`, `readonly`, `validator`, `size`) AND on any key inside the `props` object (`hint`, `placeholder`, `items`, `addLabel`, etc.). There is NO inline `when` equivalent for this β state-suffixed props REQUIRE a named state. Multiple suffixes can coexist on the same property; when more than one state is active, the last matching suffix in document order wins.',
|
|
2002
|
+
example: {
|
|
2003
|
+
$schema: "https://golemui.com/schemas/form.schema.json",
|
|
2004
|
+
states: {
|
|
2005
|
+
termsAccepted: "$form.terms === true",
|
|
2006
|
+
busy: "$meta.submitting === true"
|
|
2007
|
+
},
|
|
2008
|
+
form: [
|
|
2009
|
+
{
|
|
2010
|
+
kind: "input",
|
|
2011
|
+
type: "checkbox",
|
|
2012
|
+
path: "terms",
|
|
2013
|
+
label: "I accept the terms"
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
kind: "action",
|
|
2017
|
+
type: "button",
|
|
2018
|
+
label: "Submit",
|
|
2019
|
+
"label.termsAccepted": "Submit β",
|
|
2020
|
+
"label.busy": "Submittingβ¦",
|
|
2021
|
+
disabled: true,
|
|
2022
|
+
"disabled.termsAccepted": false,
|
|
2023
|
+
"disabled.busy": true
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
kind: "display",
|
|
2027
|
+
type: "alert",
|
|
2028
|
+
props: {
|
|
2029
|
+
level: "warning",
|
|
2030
|
+
text: "Please accept the terms to continue.",
|
|
2031
|
+
"text.termsAccepted": "Ready to submit!"
|
|
2032
|
+
},
|
|
2033
|
+
exclude: { from: ["busy"] }
|
|
2034
|
+
},
|
|
2035
|
+
{
|
|
2036
|
+
kind: "input",
|
|
2037
|
+
type: "repeater",
|
|
2038
|
+
path: "users",
|
|
2039
|
+
label: "Users",
|
|
2040
|
+
addLabel: "Add user",
|
|
2041
|
+
"addLabel.limitReached": "Limit reached β can't add more",
|
|
2042
|
+
props: {
|
|
2043
|
+
removeLabel: "Remove",
|
|
2044
|
+
template: {
|
|
2045
|
+
kind: "layout",
|
|
2046
|
+
type: "flex",
|
|
2047
|
+
props: { direction: "column" },
|
|
2048
|
+
children: [
|
|
2049
|
+
{ kind: "input", type: "textinput", path: "users.items.name", label: "Name" }
|
|
2050
|
+
]
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
]
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
],
|
|
2058
|
+
rules: [
|
|
2059
|
+
'Every state name used in `include.in`, `exclude.from`, or as a property suffix MUST be declared in the root `"states"` map.',
|
|
2060
|
+
"Child state expressions must contain only the *additional* (incremental) condition β ancestor conditions are ANDed in automatically by the runtime. Duplicating a parent condition in a child expression is wrong and redundant.",
|
|
2061
|
+
'A sub-state is only ever active when all of its ancestor states are also active. When using `include.in: ["register:adult"]` you do NOT need to also add `"register"` to the array.',
|
|
2062
|
+
"State-suffixed root props β only these support suffixes at the widget root level: `label`, `disabled`, `readonly`, `validator`, `size`. All other overridable properties live inside `props`.",
|
|
2063
|
+
'State-suffixed props inside `props` β any key inside the `props` object can be suffixed: `"hint.<state>"`, `"placeholder.<state>"`, `"items.<state>"`, `"addLabel.<state>"`, etc.',
|
|
2064
|
+
'Suffix names must not contain dots (the dot is the separator between property and state name): `"label.myState"` β
, `"label.register:adult"` β
β `"label.my.state"` β.',
|
|
2065
|
+
'Reactive expressions must reference `$form`, `$meta`, or `$formIsInvalid`. A bare identifier like `termsAccepted` without a root reference is invalid. `$formIsInvalid` is a built-in boolean (no property chain β use it as-is: `disabled: { when: "$formIsInvalid" }` or inside a state expression: `states: { formInvalid: "$formIsInvalid" }`).',
|
|
2066
|
+
"Use `===` / `!==` for equality, `&&` / `||` for logic. Avoid `=` (assignment), `==`/`!=` (loose equality), or bitwise `&`/`|`.",
|
|
2067
|
+
'When multiple states are active at the same time and a property has more than one matching suffix, the longest state name wins (most specific takes priority). Example: if both `register` and `register:adult` are active, `"label.register:adult"` overrides `"label.register"`.',
|
|
2068
|
+
"The `include.when` / `exclude.when` inline form is an alternative to named states for one-off conditions, but it cannot replace state-suffixed props β those require a named state.",
|
|
2069
|
+
'`include.in` and `exclude.from` each accept an Array of state names. A widget included `in: ["a", "b"]` renders when state `a` OR state `b` is active.',
|
|
2070
|
+
"Use optional chaining (`?.`) when accessing nested fields that may not yet exist in the form data: `$form.user?.age >= 18` not `$form.user.age >= 18`."
|
|
2071
|
+
]
|
|
2072
|
+
};
|
|
2073
|
+
const STRING_INTERPOLATION_CONCEPT = {
|
|
2074
|
+
concept: "string-interpolation",
|
|
2075
|
+
summary: "GolemUI supports live data binding in text props via `{{expression}}` template slots. Any string-valued property (e.g. `props.text`, `props.hint`, `label`) can embed one or more `{{...}}` slots. Each slot is a JavaScript-like expression evaluated against the live form state using a safe subset of JavaScript (no side effects, no function calls). i18n translation `params` objects accept a matching bare-expression format β the same expressions but without the `{{}}` delimiters.",
|
|
2076
|
+
patterns: [
|
|
2077
|
+
{
|
|
2078
|
+
name: "Template slots in display text",
|
|
2079
|
+
description: "Embed `{{expression}}` in any string prop to inject live values. Available scopes: `$form` (all current field values), `$meta` (host-supplied metadata), `$errors` (current validation error messages keyed by field uid), `$formIsInvalid` (boolean β `true` when any field currently fails validation). Use optional chaining (`?.`) when accessing nested fields that may not yet exist. Multiple slots can appear in a single string.",
|
|
2080
|
+
example: {
|
|
2081
|
+
$schema: "https://golemui.com/schemas/form.schema.json",
|
|
2082
|
+
form: [
|
|
2083
|
+
{
|
|
2084
|
+
uid: "userName",
|
|
2085
|
+
kind: "input",
|
|
2086
|
+
type: "textinput",
|
|
2087
|
+
path: "userName",
|
|
2088
|
+
validator: { type: "string", required: true }
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
uid: "submitBtn",
|
|
2092
|
+
kind: "action",
|
|
2093
|
+
type: "button",
|
|
2094
|
+
label: "Submit",
|
|
2095
|
+
actionType: "submit"
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
uid: "greeting",
|
|
2099
|
+
kind: "display",
|
|
2100
|
+
type: "alert",
|
|
2101
|
+
props: { text: "Hello {{$form.userName}}" }
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
uid: "status",
|
|
2105
|
+
kind: "display",
|
|
2106
|
+
type: "alert",
|
|
2107
|
+
props: {
|
|
2108
|
+
text: "Error: {{$errors.userName}} | Form invalid: {{$formIsInvalid}}"
|
|
2109
|
+
}
|
|
2110
|
+
},
|
|
2111
|
+
{
|
|
2112
|
+
uid: "meta-info",
|
|
2113
|
+
kind: "display",
|
|
2114
|
+
type: "alert",
|
|
2115
|
+
props: { text: "Connected as {{$meta.role}} on {{$meta.server}}" }
|
|
2116
|
+
}
|
|
2117
|
+
]
|
|
2118
|
+
}
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
name: "Expressions in slots",
|
|
2122
|
+
description: "Slots support full JavaScript-like expressions: arithmetic, string concatenation, ternary conditionals, and optional chaining. The expression is evaluated against the same scope object (`$form`, `$meta`, `$errors`, `$formIsInvalid`). If the expression evaluates to `null` or `undefined`, the slot renders as an empty string.",
|
|
2123
|
+
example: {
|
|
2124
|
+
$schema: "https://golemui.com/schemas/form.schema.json",
|
|
2125
|
+
data: { firstName: "Jane", lastName: "Doe", count: 4, role: "admin" },
|
|
2126
|
+
form: [
|
|
2127
|
+
{
|
|
2128
|
+
uid: "full-name",
|
|
2129
|
+
kind: "display",
|
|
2130
|
+
type: "alert",
|
|
2131
|
+
props: { text: "Full name: {{$form.firstName + ' ' + $form.lastName}}" }
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
uid: "next-count",
|
|
2135
|
+
kind: "display",
|
|
2136
|
+
type: "alert",
|
|
2137
|
+
props: { text: "Next item: {{$form.count + 1}}" }
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
uid: "role-label",
|
|
2141
|
+
kind: "display",
|
|
2142
|
+
type: "alert",
|
|
2143
|
+
props: { text: "Role: {{$form.role === 'admin' ? 'Administrator' : 'User'}}" }
|
|
2144
|
+
},
|
|
2145
|
+
{
|
|
2146
|
+
uid: "nested",
|
|
2147
|
+
kind: "display",
|
|
2148
|
+
type: "alert",
|
|
2149
|
+
props: { text: "City: {{$form.address?.city}}" }
|
|
2150
|
+
}
|
|
2151
|
+
]
|
|
2152
|
+
}
|
|
2153
|
+
},
|
|
2154
|
+
{
|
|
2155
|
+
name: "i18n param expressions",
|
|
2156
|
+
description: "When using i18n translations, `params` values support the same expression language as `{{}}` slots, but as **bare expressions** without the `{{}}` delimiters. Params that start with a `$` scope prefix are evaluated; others are passed as static strings. The expression has access to `$form`, `$meta`, `$errors`, and `$formIsInvalid`.",
|
|
2157
|
+
example: {
|
|
2158
|
+
$schema: "https://golemui.com/schemas/form.schema.json",
|
|
2159
|
+
data: { firstName: "Jane", lastName: "Doe", count: 4 },
|
|
2160
|
+
meta: { connectionStatus: "online" },
|
|
2161
|
+
form: [
|
|
2162
|
+
{
|
|
2163
|
+
uid: "greeting",
|
|
2164
|
+
kind: "display",
|
|
2165
|
+
type: "alert",
|
|
2166
|
+
props: {
|
|
2167
|
+
text: {
|
|
2168
|
+
key: "user.greeting",
|
|
2169
|
+
params: {
|
|
2170
|
+
hello: "Hola",
|
|
2171
|
+
fullName: "$form.firstName + ' ' + $form.lastName",
|
|
2172
|
+
n: "$form.count + 1",
|
|
2173
|
+
status: "$meta.connectionStatus"
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
]
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
],
|
|
2182
|
+
rules: [
|
|
2183
|
+
"Slots must reference at least one of `$form`, `$meta`, `$errors`, or `$formIsInvalid`. A bare identifier without a scope prefix is invalid inside `{{}}`: use `{{$form.name}}` not `{{name}}`.",
|
|
2184
|
+
"`$formIsInvalid` is a built-in boolean β use it as-is: `{{$formIsInvalid}}`. Do not chain properties onto it.",
|
|
2185
|
+
"If an expression evaluates to `null` or `undefined`, the slot renders as an empty string in display text.",
|
|
2186
|
+
"Use optional chaining (`?.`) when accessing nested fields that may not yet exist: `{{$form.address?.city}}` not `{{$form.address.city}}`.",
|
|
2187
|
+
"Do not use assignment `=` inside a slot β slots are read-only. Use `===` for equality checks.",
|
|
2188
|
+
"Slots cannot be nested: `{{$form.a {{$form.b}}}}` is invalid.",
|
|
2189
|
+
"Every `{{` must have a matching `}}`. Unbalanced delimiters cause a lint warning.",
|
|
2190
|
+
'i18n `params` values are bare expressions β do NOT wrap them in `{{}}`. Write `"$form.name"` not `"{{$form.name}}"`.',
|
|
2191
|
+
'Static string params (not starting with `$`) are passed through as-is β use them for constant values like `"Hola"` or `"px"`.',
|
|
2192
|
+
"Supported operators in expressions: arithmetic (`+`, `-`, `*`, `/`, `%`), comparison (`===`, `!==`, `<`, `>`, `<=`, `>=`), logical (`&&`, `||`, `!`), ternary (`? :`), optional chaining (`?.`), nullish coalescing (`??`).",
|
|
2193
|
+
"Expressions are evaluated using a safe subset of JavaScript β no `eval`, no function calls, no side effects."
|
|
2194
|
+
]
|
|
2195
|
+
};
|
|
2196
|
+
const CONCEPTS = {
|
|
2197
|
+
states: STATES_CONCEPT,
|
|
2198
|
+
"string-interpolation": STRING_INTERPOLATION_CONCEPT
|
|
2199
|
+
};
|
|
2200
|
+
function getConcept(input) {
|
|
2201
|
+
const result = CONCEPTS[input.concept];
|
|
2202
|
+
if (!result) {
|
|
2203
|
+
const known = Object.keys(CONCEPTS).sort().map((c) => `\`${c}\``).join(", ");
|
|
2204
|
+
throw new Error(`Unknown concept \`${input.concept}\`. Known concepts: ${known}.`);
|
|
2205
|
+
}
|
|
2206
|
+
return result;
|
|
2207
|
+
}
|
|
2208
|
+
const GET_CONCEPT_TOOL = {
|
|
2209
|
+
name: "get_concept",
|
|
2210
|
+
description: 'Return a detailed guide for a cross-cutting GolemUI form concept β things that span multiple widgets and affect the whole form, rather than the API of a single widget. Call this when you need to: (1) change a widget\'s props based on form state (state-suffixed props like `"label.stateName": "β¦"`), or (2) reuse the same condition across multiple widgets (`include: { in: [β¦] }` / `exclude: { from: [β¦] }`). For a one-off show/hide on a single widget, use `include: { when: "β¦" }` or `exclude: { when: "β¦" }` directly β no states needed, no need to call this tool. Currently supported concepts: `states`, `string-interpolation`.',
|
|
2211
|
+
inputSchema: {
|
|
2212
|
+
type: "object",
|
|
2213
|
+
properties: {
|
|
2214
|
+
concept: {
|
|
2215
|
+
type: "string",
|
|
2216
|
+
description: 'The concept to explain. Currently supported: `"states"`, `"string-interpolation"`.',
|
|
2217
|
+
enum: Object.keys(CONCEPTS)
|
|
2218
|
+
}
|
|
2219
|
+
},
|
|
2220
|
+
required: ["concept"]
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
1807
2223
|
const EXAMPLES = {
|
|
1808
2224
|
accordion: {
|
|
1809
2225
|
kind: "layout",
|
|
@@ -2021,7 +2437,8 @@ const EXAMPLES = {
|
|
|
2021
2437
|
const NOTES = {
|
|
2022
2438
|
textinput: [
|
|
2023
2439
|
"`path` is the dot-path into form data this field writes to.",
|
|
2024
|
-
"`validator.format` supports: `email`, `hostname`, `ipv4`, `ipv6`, `url`, `uuid`, `date`, `time`, `date-time`, `duration`."
|
|
2440
|
+
"`validator.format` supports: `email`, `hostname`, `ipv4`, `ipv6`, `url`, `uuid`, `date`, `time`, `date-time`, `duration`.",
|
|
2441
|
+
'Root props `label`, `disabled`, `readonly`, `validator`, and `size` accept state suffixes β e.g. `"label.<stateName>": "New label"` overrides the label only when that named state is active. Props inside `props` (e.g. `hint`, `placeholder`) also accept suffixes as `"hint.<stateName>"`. Call `get_concept({ concept: "states" })` for the full pattern.'
|
|
2025
2442
|
],
|
|
2026
2443
|
markdownText: [
|
|
2027
2444
|
"Display-only widget for rendering markdown. Can be used as a top-level form widget (inside any layout) or inside templates like `dropdown.props.items[].template`.",
|
|
@@ -2053,13 +2470,19 @@ const NOTES = {
|
|
|
2053
2470
|
repeater: [
|
|
2054
2471
|
"`props.template` must be a layout widget (flex/grid/tabs/accordion) whose children are the per-item fields.",
|
|
2055
2472
|
'Child paths inside the template MUST follow the form `<repeater.path>.items.<fieldName>`. The `items` segment is reserved β the runtime substitutes it with the current array index per row. For example, a repeater at `path: "users"` with a child `firstName` uses `path: "users.items.firstName"`. Plain `firstName` will NOT bind to the array.',
|
|
2056
|
-
'Nested repeaters chain the convention: a repeater at `path: "teams"` whose template contains a repeater at `path: "teams.items.members"` whose children use `path: "teams.items.members.items.<field>"`.'
|
|
2473
|
+
'Nested repeaters chain the convention: a repeater at `path: "teams"` whose template contains a repeater at `path: "teams.items.members"` whose children use `path: "teams.items.members.items.<field>"`.',
|
|
2474
|
+
'`addLabel` supports state suffixes: `"addLabel.<stateName>": "Limit reached"` swaps the add-button label when a named state is active β useful for capping array length. Call `get_concept({ concept: "states" })` for the full pattern.'
|
|
2057
2475
|
],
|
|
2058
2476
|
button: [
|
|
2059
2477
|
"`actionType` controls the button's role. `actionType: \"submit\"` makes the button fire the form's `formSubmit` event natively β the host listens for it via `(formSubmit)` (Angular), `@formSubmit` (Vue), `onFormSubmit` (React), or the `form-submit` event (Lit). No custom handler needed. Use this for the primary submit button on a form.",
|
|
2060
|
-
'`actionType: "button"` (the default, can be omitted) is a regular action button. Wire it via `on.click: "<handlerName>"` where `<handlerName>` is registered in the form config\'s event handlers.'
|
|
2478
|
+
'`actionType: "button"` (the default, can be omitted) is a regular action button. Wire it via `on.click: "<handlerName>"` where `<handlerName>` is registered in the form config\'s event handlers.',
|
|
2479
|
+
'Supports state-suffixed props: `"label.<stateName>"` and `"disabled.<stateName>"` swap the label or disabled state when a named state is active β e.g. disable the submit button until terms are accepted, then re-enable it. Call `get_concept({ concept: "states" })` for the full pattern.'
|
|
2061
2480
|
],
|
|
2062
|
-
checkbox: ["Set `validator.const: true` to require the user to tick it (e.g. terms acceptance)."]
|
|
2481
|
+
checkbox: ["Set `validator.const: true` to require the user to tick it (e.g. terms acceptance)."],
|
|
2482
|
+
alert: [
|
|
2483
|
+
'Use `include: { in: ["stateName"] }` to show this alert only when a named state is active, or `exclude: { from: ["stateName"] }` to hide it when a state is active. This is cleaner than `include: { when: "..." }` when the same condition is reused across multiple widgets. Call `get_concept({ concept: "states" })` for the full states pattern.',
|
|
2484
|
+
'Props inside `props` (e.g. `text`, `level`) accept state suffixes: `"text.<stateName>": "New message"` swaps the message when that state is active.'
|
|
2485
|
+
]
|
|
2063
2486
|
};
|
|
2064
2487
|
function synthesizeExample(widgetType, schema) {
|
|
2065
2488
|
const props = schema["properties"];
|
|
@@ -2115,11 +2538,13 @@ const TOOLS = [
|
|
|
2115
2538
|
VALIDATE_FORM_DEFINITION_TOOL,
|
|
2116
2539
|
GENERATE_FROM_JSON_SCHEMA_TOOL,
|
|
2117
2540
|
GENERATE_FROM_OPENAPI_TOOL,
|
|
2118
|
-
GET_WIDGET_SPEC_TOOL
|
|
2541
|
+
GET_WIDGET_SPEC_TOOL,
|
|
2542
|
+
GET_CONCEPT_TOOL
|
|
2119
2543
|
];
|
|
2544
|
+
const SERVER_INSTRUCTIONS = 'This server builds and validates GolemUI form definitions β declarative, JSON-serializable forms shaped as `{ form: [...widgets], states?: {...} }`. Its job is to help you produce a form definition that is guaranteed correct before the user pastes it into their codebase.\n\nRecommended workflow:\n1. Starting from an existing schema? Use a generator β both return a pre-validated definition, so check the returned `unmapped` list and surface anything left over to the user. For a raw JSON Schema (e.g. an API request body), call `generate_from_json_schema`. For an OpenAPI 3.x spec, call `generate_from_openapi`: pass `operation` as "METHOD /path" (e.g. "POST /users") or an exact operationId, plus the spec as a parsed `document` or a `documentUrl` to fetch β it resolves the operation\'s request body, dereferences `$ref`s, and falls back to the operation\'s parameters when there is no request body.\n2. Building or editing by hand? Look up a single widget with `get_widget_spec` (its `kind`, `props`, and `validator` shape), and cross-cutting behavior that spans widgets β conditional rendering, per-state prop overrides β with `get_concept`.\n3. ALWAYS finish by calling `validate_form_definition`. It checks the definition against the bundled JSON Schemas and returns `{ valid, errors, warnings, expressionWarnings }`. Treat `errors` as blocking: fix them and re-validate until `valid` is true. `warnings` (likely-custom widgets) and `expressionWarnings` (linted reactive expressions) are advisory β surface them, but they do not flip `valid`.\n\nDo not hand a form definition to the user until `validate_form_definition` reports `valid: true`.';
|
|
2120
2545
|
const server = new Server(
|
|
2121
2546
|
{ name: PKG_NAME, version: PKG_VERSION },
|
|
2122
|
-
{ capabilities: { tools: {} } }
|
|
2547
|
+
{ capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS }
|
|
2123
2548
|
);
|
|
2124
2549
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
2125
2550
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -2134,6 +2559,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2134
2559
|
return ok(await generateFromOpenapi(args));
|
|
2135
2560
|
case "get_widget_spec":
|
|
2136
2561
|
return ok(getWidgetSpec(args));
|
|
2562
|
+
case "get_concept":
|
|
2563
|
+
return ok(getConcept(args));
|
|
2137
2564
|
default:
|
|
2138
2565
|
return err(`Unknown tool: ${name}`);
|
|
2139
2566
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@golemui/gui-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Model Context Protocol server for GolemUI β gives AI coding assistants deterministic schema validation and form generation for GolemUI form definitions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
33
33
|
"ajv": "^8.17.1",
|
|
34
|
-
"ajv-formats": "^3.0.1"
|
|
34
|
+
"ajv-formats": "^3.0.1",
|
|
35
|
+
"@golemui/gui-schemas": "0.16.0"
|
|
35
36
|
},
|
|
36
37
|
"engines": {
|
|
37
38
|
"node": ">=18.0.0"
|