@tai42/studio-sdk 9.2.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/SECURITY.md +10 -2
- package/dist/index.d.ts +18 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -6
- package/dist/index.js.map +1 -1
- package/dist/schema-form/SchemaForm.d.ts +21 -1
- package/dist/schema-form/SchemaForm.d.ts.map +1 -1
- package/dist/schema-form/SchemaForm.js +8 -3
- package/dist/schema-form/SchemaForm.js.map +1 -1
- package/dist/schema-form/context.d.ts +73 -1
- package/dist/schema-form/context.d.ts.map +1 -1
- package/dist/schema-form/context.js +16 -2
- package/dist/schema-form/context.js.map +1 -1
- package/dist/schema-form/index.d.ts +27 -0
- package/dist/schema-form/index.d.ts.map +1 -1
- package/dist/schema-form/index.js +26 -0
- package/dist/schema-form/index.js.map +1 -1
- package/dist/schema-form/string-field.d.ts +15 -16
- package/dist/schema-form/string-field.d.ts.map +1 -1
- package/dist/schema-form/string-field.js +43 -50
- package/dist/schema-form/string-field.js.map +1 -1
- package/package.json +9 -5
- package/src/index.ts +27 -26
- package/src/schema-form/SchemaForm.tsx +46 -14
- package/src/schema-form/context.ts +86 -3
- package/src/schema-form/index.ts +32 -0
- package/src/schema-form/string-field.tsx +64 -70
- package/dist/jq.d.ts +0 -23
- package/dist/jq.d.ts.map +0 -1
- package/dist/jq.js +0 -33
- package/dist/jq.js.map +0 -1
- package/src/jq.ts +0 -53
|
@@ -1,40 +1,33 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense,
|
|
2
|
+
import { Suspense, useCallback, useContext, useMemo, useState } from 'react';
|
|
3
3
|
import { CompletionInput } from '../components/completion-input';
|
|
4
4
|
import { Field } from '../components/field';
|
|
5
5
|
import { AlertTriangleIcon } from '../components/icons';
|
|
6
6
|
import { TextInput, Textarea } from '../components/inputs';
|
|
7
7
|
import { errorMessage } from '../errors';
|
|
8
|
-
import { CompletionProviderContext } from './context';
|
|
8
|
+
import { CompletionProviderContext, ExpressionFieldContext } from './context';
|
|
9
9
|
import { MediaField } from './media-field';
|
|
10
|
-
// The lazily-loaded jq door. Defining it at module scope (never inside a
|
|
11
|
-
// component) is what keeps the lazy component's identity stable across renders,
|
|
12
|
-
// so React resolves the chunk once and never re-suspends a mounted field. The
|
|
13
|
-
// `import('../jq')` here is a DYNAMIC import — the sole runtime edge from
|
|
14
|
-
// `schema-form` to the jq door — so the visual editor, worker, and wasm live in
|
|
15
|
-
// a split chunk that a non-annotated form never requests. The `type`-only
|
|
16
|
-
// import of `JqInputShapeDescriptor` above is erased at compile time and adds no
|
|
17
|
-
// such edge.
|
|
18
|
-
const JqField = lazy(async () => {
|
|
19
|
-
const { JqField } = await import('../jq');
|
|
20
|
-
return { default: JqField };
|
|
21
|
-
});
|
|
22
10
|
export function StringField({ heading, description, error, format, media, expression, argName, value, required, onChange, }) {
|
|
23
11
|
const completionProvider = useContext(CompletionProviderContext);
|
|
12
|
+
const expressionField = useContext(ExpressionFieldContext);
|
|
24
13
|
const current = typeof value === 'string' ? value : '';
|
|
25
14
|
// An empty optional field emits `undefined` (drops the key); a required one
|
|
26
15
|
// keeps the empty string so validation can flag it.
|
|
27
16
|
const emit = (next) => {
|
|
28
17
|
onChange(next === '' && !required ? undefined : next);
|
|
29
18
|
};
|
|
30
|
-
// An expression-annotated field renders the
|
|
31
|
-
//
|
|
19
|
+
// An expression-annotated field renders the injected door — the resting control
|
|
20
|
+
// plus its visual-editor button. Checked before `media` because the expression
|
|
32
21
|
// annotation is the more specific, deliberate opt-in (`x-tai42-expression`);
|
|
33
|
-
// a schema carrying both is contradictory, and
|
|
22
|
+
// a schema carrying both is contradictory, and an expression is text, not an
|
|
34
23
|
// upload. Not reachable together with the completion provider path: an
|
|
35
24
|
// expression is authored, not completed from server suggestions.
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
//
|
|
26
|
+
// With no door injected the annotation is INERT: the field falls through to the
|
|
27
|
+
// ordinary paths below, exactly as a malformed annotation does, so a form
|
|
28
|
+
// renders the same bytes either way.
|
|
29
|
+
if (expression !== undefined && expressionField !== undefined) {
|
|
30
|
+
return (_jsx(ExpressionField, { component: expressionField, heading: heading, description: description, error: error, expression: expression, argName: argName, value: current, onChange: emit }));
|
|
38
31
|
}
|
|
39
32
|
// A media-annotated field renders the upload control instead of a text box.
|
|
40
33
|
if (media !== undefined) {
|
|
@@ -74,51 +67,51 @@ function CompletionField({ argName, value, onChange, provider, }) {
|
|
|
74
67
|
}, fetchCompletions: fetchCompletions, onError: onError }), failure === null ? null : (_jsxs("p", { role: "status", className: "tai-field-hint", style: { margin: 0 }, children: [_jsx(AlertTriangleIcon, {}), `Suggestions are unavailable: ${failure}`] }))] }));
|
|
75
68
|
}
|
|
76
69
|
/**
|
|
77
|
-
* An expression-annotated string field, rendered
|
|
78
|
-
* resting control with the
|
|
79
|
-
* its own label/description/error chrome (a11y-linked slots), so it is NOT
|
|
80
|
-
* wrapped in the form's `Field` component — the annotation's descriptor fields
|
|
81
|
-
*
|
|
82
|
-
* feeds the `error` prop.
|
|
70
|
+
* An expression-annotated string field, rendered through the injected door: a
|
|
71
|
+
* multiline resting control with the door's own visual-editor button. The door
|
|
72
|
+
* brings its own label/description/error chrome (a11y-linked slots), so it is NOT
|
|
73
|
+
* wrapped in the form's `Field` component — the annotation's descriptor fields map
|
|
74
|
+
* onto the input-shape descriptor, and the form's per-field error feeds `error`.
|
|
83
75
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* - `onEditorOpenChange` — the hosts that render `SchemaForm` register no global
|
|
88
|
-
* keyboard shortcuts, so there is nothing to mute while the editor is open.
|
|
89
|
-
* - `compact` — the form renders every sibling field with a visible label;
|
|
90
|
-
* the compact variant (visually-hidden label, icon-only door) is for dense
|
|
91
|
-
* host rows, which the schema form does not have.
|
|
76
|
+
* The door is rendered as an ELEMENT, never called as a function, so its hooks and
|
|
77
|
+
* state (editor open, worker handle) belong to it and survive re-renders of this
|
|
78
|
+
* field.
|
|
92
79
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* label/description/error chrome around the current
|
|
96
|
-
* multiline control — so the field holds its layout and keeps
|
|
97
|
-
* while
|
|
80
|
+
* It mounts inside a `Suspense` boundary because a host is free to inject a `lazy`
|
|
81
|
+
* door to keep the jq subgraph in a split chunk of ITS OWN bundle. The fallback
|
|
82
|
+
* paints the resting shell — label/description/error chrome around the current
|
|
83
|
+
* value in a read-only multiline control — so the field holds its layout and keeps
|
|
84
|
+
* the value visible while that chunk resolves, and the swap does not flash. A door
|
|
85
|
+
* injected eagerly never suspends and renders straight through.
|
|
86
|
+
*
|
|
87
|
+
* The form passes only the props the field owns; a jq door's `serverValidate`,
|
|
88
|
+
* `onEditorOpenChange`, and `compact` stay unwired, because no author-time
|
|
89
|
+
* validation endpoint applies to a schema-declared field, the hosts that render
|
|
90
|
+
* `SchemaForm` register no global shortcuts to mute, and every sibling field shows
|
|
91
|
+
* a visible label (the compact variant is for dense host rows).
|
|
98
92
|
*/
|
|
99
|
-
function
|
|
100
|
-
// Memoised: the descriptor's identity feeds
|
|
93
|
+
function ExpressionField({ component: Door, heading, description, error, expression, argName, value, onChange, }) {
|
|
94
|
+
// Memoised: the descriptor's identity feeds the door's own memoisation, so a
|
|
101
95
|
// fresh object each keystroke would defeat it.
|
|
102
96
|
const shape = useMemo(() => expressionShape(expression, argName), [expression, argName]);
|
|
103
|
-
return (_jsx(Suspense, { fallback: _jsx(
|
|
97
|
+
return (_jsx(Suspense, { fallback: _jsx(ExpressionFieldSkeleton, { heading: heading, description: description, error: error, value: value }), children: _jsx(Door, { label: heading, description: description, error: error, shape: shape, multiline: true, value: value, onChange: onChange }) }));
|
|
104
98
|
}
|
|
105
99
|
/**
|
|
106
|
-
* The resting shell shown while
|
|
107
|
-
* door's footprint — the same label/description/error chrome wrapping a
|
|
100
|
+
* The resting shell shown while a lazily-injected door resolves. It mirrors the
|
|
101
|
+
* live door's footprint — the same label/description/error chrome wrapping a
|
|
108
102
|
* multiline control seeded with the current value — but the control is inert
|
|
109
103
|
* (read-only, `aria-busy`) because there is nothing to edit yet. Matching the
|
|
110
104
|
* footprint is what keeps the Suspense swap from shifting layout or flashing.
|
|
111
105
|
*/
|
|
112
|
-
function
|
|
106
|
+
function ExpressionFieldSkeleton({ heading, description, error, value, }) {
|
|
113
107
|
return (_jsx(Field, { label: heading, description: description, error: error, children: _jsx(Textarea, { value: value, readOnly: true, "aria-busy": "true" }) }));
|
|
114
108
|
}
|
|
115
109
|
/**
|
|
116
|
-
* Map a classified expression annotation onto
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* than invented copy.
|
|
110
|
+
* Map a classified expression annotation onto the door's input-shape descriptor.
|
|
111
|
+
* A bare annotation (`language` only) maps to NO descriptor — an undeclared shape
|
|
112
|
+
* is the honest rendering when the server said nothing about `.`. When any
|
|
113
|
+
* descriptor field is present, the required descriptor members the annotation
|
|
114
|
+
* omits fall back to neutral values rather than invented copy.
|
|
122
115
|
*/
|
|
123
116
|
function expressionShape(expression, argName) {
|
|
124
117
|
const declared = expression.label !== undefined ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string-field.js","sourceRoot":"","sources":["../../src/schema-form/string-field.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"string-field.js","sourceRoot":"","sources":["../../src/schema-form/string-field.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tai42/studio-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "The Studio plugin contract, shared UI components, and hooks that every feature builds on.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "tai42 <oss@tai42.ai>",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./dist/testing.d.ts",
|
|
45
45
|
"import": "./dist/testing.js"
|
|
46
46
|
},
|
|
47
|
+
"./schema-form": {
|
|
48
|
+
"types": "./dist/schema-form/index.d.ts",
|
|
49
|
+
"import": "./dist/schema-form/index.js"
|
|
50
|
+
},
|
|
47
51
|
"./tokens.css": "./dist/components/tokens.css",
|
|
48
52
|
"./fonts.css": "./dist/components/fonts.css",
|
|
49
53
|
"./components.css": "./dist/components/components.css",
|
|
@@ -69,6 +73,7 @@
|
|
|
69
73
|
},
|
|
70
74
|
"devDependencies": {
|
|
71
75
|
"@microsoft/api-extractor": "7.58.12",
|
|
76
|
+
"@tai42/jq-studio": "^0.1.3",
|
|
72
77
|
"@testing-library/jest-dom": "^6.9.1",
|
|
73
78
|
"@testing-library/react": "^16.3.2",
|
|
74
79
|
"@testing-library/user-event": "^14.6.6",
|
|
@@ -90,8 +95,7 @@
|
|
|
90
95
|
"@radix-ui/react-select": "^2.3.2",
|
|
91
96
|
"@radix-ui/react-tabs": "^1.1.16",
|
|
92
97
|
"@radix-ui/react-tooltip": "^1.2.11",
|
|
93
|
-
"@tai42/
|
|
94
|
-
"@tai42/api-client": "9.2.1"
|
|
98
|
+
"@tai42/api-client": "10.0.0"
|
|
95
99
|
},
|
|
96
100
|
"scripts": {
|
|
97
101
|
"build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
@@ -99,7 +103,7 @@
|
|
|
99
103
|
"lint": "eslint src scripts --max-warnings 0",
|
|
100
104
|
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\" \"scripts/**/*.mjs\"",
|
|
101
105
|
"test": "vitest run",
|
|
102
|
-
"api:check": "api-extractor run --config config/api-extractor.index.json && api-extractor run --config config/api-extractor.host.json && api-extractor run --config config/api-extractor.testing.json",
|
|
103
|
-
"api:update": "api-extractor run --local --config config/api-extractor.index.json && api-extractor run --local --config config/api-extractor.host.json && api-extractor run --local --config config/api-extractor.testing.json"
|
|
106
|
+
"api:check": "api-extractor run --config config/api-extractor.index.json && api-extractor run --config config/api-extractor.host.json && api-extractor run --config config/api-extractor.testing.json && api-extractor run --config config/api-extractor.schema-form.json",
|
|
107
|
+
"api:update": "api-extractor run --local --config config/api-extractor.index.json && api-extractor run --local --config config/api-extractor.host.json && api-extractor run --local --config config/api-extractor.testing.json && api-extractor run --local --config config/api-extractor.schema-form.json"
|
|
104
108
|
}
|
|
105
109
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,9 +11,22 @@
|
|
|
11
11
|
* exported only from `@tai42/studio-sdk/host`, which the host bundle imports. See
|
|
12
12
|
* SECURITY.md for the trust boundary.
|
|
13
13
|
*
|
|
14
|
-
* The
|
|
15
|
-
* across the plugin boundary; this package imports nothing internal at
|
|
16
|
-
* (the `ApiClient`/`Interaction` types are type-only imports).
|
|
14
|
+
* The shared modules (react, react-dom, @tai42/studio-sdk, @tai42/jq-studio) are
|
|
15
|
+
* singletons across the plugin boundary; this package imports nothing internal at
|
|
16
|
+
* runtime (the `ApiClient`/`Interaction` types are type-only imports).
|
|
17
|
+
*
|
|
18
|
+
* NO JQ. The visual jq editor is `@tai42/jq-studio`, a separately published,
|
|
19
|
+
* client-agnostic package, and this barrel does not re-export it — not even as a
|
|
20
|
+
* thin pass-through. A bundler emits a package's Web Worker file and wasm engine
|
|
21
|
+
* from the mere PRESENCE of the module in the graph (the assets are emitted while
|
|
22
|
+
* the module is transformed, before tree-shaking can drop an unused re-export), so
|
|
23
|
+
* a pass-through here would put ~2.9MB of jq runtime into every consumer that
|
|
24
|
+
* imports this barrel for a Button. Consumers that want the editor import
|
|
25
|
+
* `@tai42/jq-studio` directly; the import map resolves that bare specifier to the
|
|
26
|
+
* one served copy, so the whole deployment still shares a single jq instance, a
|
|
27
|
+
* single primitives injection, and a single worker. What the SDK keeps is the
|
|
28
|
+
* INJECTION POINT — `ExpressionFieldContext` / `SchemaForm`'s `expressionField`
|
|
29
|
+
* prop, plus the mirrored contract types — which holds no edge to jq at all.
|
|
17
30
|
*/
|
|
18
31
|
// The design system, delivered by the BARREL so each host app loads it once and a
|
|
19
32
|
// plugin never ships its own copy. These are bare side-effect imports, so they
|
|
@@ -48,29 +61,6 @@ export type {
|
|
|
48
61
|
export { STUDIO_PLUGIN_API_VERSION, checkPluginApiVersion } from './plugin/version';
|
|
49
62
|
export type { VersionGateResult } from './plugin/version';
|
|
50
63
|
|
|
51
|
-
// -- jq fields (re-exported from @tai42/jq-studio; the platform owns no jq code) --
|
|
52
|
-
export {
|
|
53
|
-
JqField,
|
|
54
|
-
JQEditorDialog,
|
|
55
|
-
JqEditorDialog,
|
|
56
|
-
preloadJq,
|
|
57
|
-
installDefaultJqWorker,
|
|
58
|
-
PrimitivesProvider,
|
|
59
|
-
} from './jq';
|
|
60
|
-
export type {
|
|
61
|
-
JqFieldProps,
|
|
62
|
-
JQEditorDialogProps,
|
|
63
|
-
JqEditorDialogProps,
|
|
64
|
-
ExpressionLanguage,
|
|
65
|
-
JqInputKey,
|
|
66
|
-
JqInputShapeDescriptor,
|
|
67
|
-
SampleInputProvider,
|
|
68
|
-
ServerValidationResult,
|
|
69
|
-
ServerValidateHook,
|
|
70
|
-
JqFieldDeclaration,
|
|
71
|
-
Primitives,
|
|
72
|
-
} from './jq';
|
|
73
|
-
|
|
74
64
|
// -- Hooks -------------------------------------------------------------------
|
|
75
65
|
export { ApiProvider, useApi } from './hooks/useApi';
|
|
76
66
|
export type { ApiClient } from './hooks/useApi';
|
|
@@ -372,6 +362,13 @@ export type { IconProps, IconComponent } from './components/icons';
|
|
|
372
362
|
export {
|
|
373
363
|
SchemaForm,
|
|
374
364
|
RecordEntryRendererContext,
|
|
365
|
+
// The expression-door injection point: a host that wants `x-tai42-expression`
|
|
366
|
+
// fields to author through the visual editor imports `JqField` from
|
|
367
|
+
// `@tai42/jq-studio` and hands it to a form (the `expressionField` prop) or
|
|
368
|
+
// mounts this context above its tree. A form with no door renders those fields
|
|
369
|
+
// as plain string inputs and stays free of the jq subgraph — the editor, its
|
|
370
|
+
// worker, and its wasm — entirely.
|
|
371
|
+
ExpressionFieldContext,
|
|
375
372
|
SecretRefField,
|
|
376
373
|
defaultValueForSchema,
|
|
377
374
|
validateAgainstSchema,
|
|
@@ -382,6 +379,10 @@ export type {
|
|
|
382
379
|
CompletionProvider,
|
|
383
380
|
RecordEntryRenderer,
|
|
384
381
|
RecordEntryContext,
|
|
382
|
+
ExpressionFieldComponent,
|
|
383
|
+
ExpressionFieldProps,
|
|
384
|
+
ExpressionInputShape,
|
|
385
|
+
ExpressionInputKey,
|
|
385
386
|
SecretRefFieldProps,
|
|
386
387
|
SecretRef,
|
|
387
388
|
JsonSchema,
|
|
@@ -32,10 +32,27 @@
|
|
|
32
32
|
* validator ignores it); these client-side caps are UX guards, and the SERVER
|
|
33
33
|
* MUST independently validate the decoded size. The base64 decoding and cap
|
|
34
34
|
* checks live in the sibling `media` module.
|
|
35
|
+
*
|
|
36
|
+
* EXPRESSION FIELDS ARE OPT-IN. A string field carrying `x-tai42-expression`
|
|
37
|
+
* renders an expression-authoring door — `JqField` from `@tai42/jq-studio` — only
|
|
38
|
+
* when the host
|
|
39
|
+
* INJECTS one, through {@link SchemaFormProps.expressionField} or the ambient
|
|
40
|
+
* {@link ExpressionFieldContext}; with no door it renders the plain string input.
|
|
41
|
+
* The form owns no edge to the door: importing it here, even dynamically behind
|
|
42
|
+
* `lazy`, would EMIT the visual editor, its worker file, and a multi-megabyte wasm
|
|
43
|
+
* engine into every consumer that bundles the SDK to render forms, whether or not
|
|
44
|
+
* a form ever authors an expression. Injection puts that weight on the hosts that
|
|
45
|
+
* want it and only there.
|
|
35
46
|
*/
|
|
36
47
|
import type { ReactNode } from 'react';
|
|
48
|
+
import { useContext } from 'react';
|
|
37
49
|
|
|
38
|
-
import {
|
|
50
|
+
import type { ExpressionFieldComponent } from './context';
|
|
51
|
+
import {
|
|
52
|
+
CompletionProviderContext,
|
|
53
|
+
ExpressionFieldContext,
|
|
54
|
+
MaxUploadBytesContext,
|
|
55
|
+
} from './context';
|
|
39
56
|
import { FieldNode } from './field-node';
|
|
40
57
|
import { DEFAULT_MAX_UPLOAD_BYTES } from './media';
|
|
41
58
|
import { stackClass } from './styles';
|
|
@@ -70,6 +87,14 @@ export interface SchemaFormProps {
|
|
|
70
87
|
* absent both, {@link DEFAULT_MAX_UPLOAD_BYTES} applies.
|
|
71
88
|
*/
|
|
72
89
|
readonly maxUploadBytes?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Optional. The expression-authoring door for `x-tai42-expression` fields —
|
|
92
|
+
* `JqField` from `@tai42/jq-studio`, which the host imports directly and passes
|
|
93
|
+
* here (see the module doc-comment). Omitted, the enclosing
|
|
94
|
+
* {@link ExpressionFieldContext} supplies it; absent both, an annotated field
|
|
95
|
+
* renders the plain string input.
|
|
96
|
+
*/
|
|
97
|
+
readonly expressionField?: ExpressionFieldComponent;
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
/**
|
|
@@ -84,23 +109,30 @@ export function SchemaForm({
|
|
|
84
109
|
idPrefix = 'schema-form',
|
|
85
110
|
completionProvider,
|
|
86
111
|
maxUploadBytes = DEFAULT_MAX_UPLOAD_BYTES,
|
|
112
|
+
expressionField,
|
|
87
113
|
}: SchemaFormProps): ReactNode {
|
|
114
|
+
// The prop overrides the ambient door for this form only; omitted, the
|
|
115
|
+
// enclosing one flows through unchanged (providing `undefined` would BLIND the
|
|
116
|
+
// form to a door the host wired above it).
|
|
117
|
+
const ambientExpressionField = useContext(ExpressionFieldContext);
|
|
88
118
|
return (
|
|
89
119
|
<CompletionProviderContext.Provider value={completionProvider}>
|
|
90
120
|
<MaxUploadBytesContext.Provider value={maxUploadBytes}>
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
121
|
+
<ExpressionFieldContext.Provider value={expressionField ?? ambientExpressionField}>
|
|
122
|
+
<div className={stackClass} data-testid={idPrefix}>
|
|
123
|
+
<FieldNode
|
|
124
|
+
schema={schema}
|
|
125
|
+
root={schema}
|
|
126
|
+
value={value}
|
|
127
|
+
onChange={onChange}
|
|
128
|
+
path=""
|
|
129
|
+
label={undefined}
|
|
130
|
+
required
|
|
131
|
+
errors={errors}
|
|
132
|
+
idPrefix={idPrefix}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</ExpressionFieldContext.Provider>
|
|
104
136
|
</MaxUploadBytesContext.Provider>
|
|
105
137
|
</CompletionProviderContext.Provider>
|
|
106
138
|
);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ambient React contexts for the schema-form tree: the optional argument
|
|
3
|
-
* completion provider
|
|
4
|
-
*
|
|
3
|
+
* completion provider, the default media-upload byte cap, the record-entry value
|
|
4
|
+
* renderer, and the expression-field door. A field deep in the tree reads these
|
|
5
|
+
* here rather than every intermediate node drilling the prop.
|
|
5
6
|
*/
|
|
6
7
|
import { createContext } from 'react';
|
|
7
|
-
import type { ReactNode } from 'react';
|
|
8
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
8
9
|
|
|
9
10
|
import { DEFAULT_MAX_UPLOAD_BYTES } from './media';
|
|
10
11
|
import type { CompletionProvider } from './SchemaForm';
|
|
@@ -44,3 +45,85 @@ export type RecordEntryRenderer = (entry: RecordEntryContext) => ReactNode;
|
|
|
44
45
|
// Ambient record-entry value renderer for the whole form; the record field reads
|
|
45
46
|
// it here so intermediate object/array/record nodes never drill the prop.
|
|
46
47
|
export const RecordEntryRendererContext = createContext<RecordEntryRenderer | undefined>(undefined);
|
|
48
|
+
|
|
49
|
+
/** One top-level key of the expression's input document, with a one-line gloss. */
|
|
50
|
+
export interface ExpressionInputKey {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly gloss: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* What `.` IS for an expression-annotated field, as the form hands it to the
|
|
57
|
+
* injected door. Built by the renderer from the schema's `x-tai42-expression`
|
|
58
|
+
* annotation.
|
|
59
|
+
*
|
|
60
|
+
* Like the annotation type it is built from, this MIRRORS jq-studio's input-shape
|
|
61
|
+
* descriptor without importing it: the schema-form tree names no jq type, in code
|
|
62
|
+
* OR in its declarations, which is what keeps a consumer that never injects a door
|
|
63
|
+
* free of the jq subgraph. `JqField` satisfies it structurally; a test pins that.
|
|
64
|
+
*/
|
|
65
|
+
export interface ExpressionInputShape {
|
|
66
|
+
/** Stable, host-namespaced id, opaque to the door (memoisation/telemetry). */
|
|
67
|
+
readonly id: string;
|
|
68
|
+
/** Short chip label — what `.` is here, e.g. "node envelope". */
|
|
69
|
+
readonly label: string;
|
|
70
|
+
/** One sentence: what `.` is in this field. */
|
|
71
|
+
readonly blurb: string;
|
|
72
|
+
/** The top-level keys of `.`, each with a one-liner. */
|
|
73
|
+
readonly keys: readonly ExpressionInputKey[];
|
|
74
|
+
/** What the expression must RETURN, e.g. "true or false". */
|
|
75
|
+
readonly returns: string;
|
|
76
|
+
/** Per-field caveats. */
|
|
77
|
+
readonly caveats?: readonly string[];
|
|
78
|
+
/** A static skeleton of `.` for the door's test surface. */
|
|
79
|
+
readonly sample?: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Exactly the props the form hands an injected expression field — no more, so any
|
|
84
|
+
* expression editor can satisfy the contract, and no less, so the form is free to
|
|
85
|
+
* pass all of them. The label/description/error slots are the door's own chrome:
|
|
86
|
+
* an expression field brings its a11y-linked label and helper slots, so the form
|
|
87
|
+
* does NOT wrap it in the usual `Field`.
|
|
88
|
+
*/
|
|
89
|
+
export interface ExpressionFieldProps {
|
|
90
|
+
readonly label: string;
|
|
91
|
+
readonly value: string;
|
|
92
|
+
readonly onChange: (value: string) => void;
|
|
93
|
+
readonly description?: ReactNode;
|
|
94
|
+
readonly error?: ReactNode;
|
|
95
|
+
readonly shape?: ExpressionInputShape;
|
|
96
|
+
/** Render a multiline resting control; the form always asks for one. */
|
|
97
|
+
readonly multiline?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The expression-authoring door a host INJECTS into the form — `JqField`, which the
|
|
102
|
+
* host imports straight from `@tai42/jq-studio`.
|
|
103
|
+
*
|
|
104
|
+
* It is a COMPONENT, rendered as an element, never called as a function: the door
|
|
105
|
+
* owns hooks and state (editor open, worker handle), which only survive as its own
|
|
106
|
+
* element identity. A host may inject a `lazy` door to keep the jq subgraph in a
|
|
107
|
+
* split chunk — the form mounts it inside a Suspense boundary that paints the
|
|
108
|
+
* resting shell while that chunk resolves.
|
|
109
|
+
*/
|
|
110
|
+
// The injected reference must be STABLE across renders (a module-level
|
|
111
|
+
// component or memoized value): an inline arrow remounts the door — and drops
|
|
112
|
+
// its open-editor state — on every parent render.
|
|
113
|
+
export type ExpressionFieldComponent = ComponentType<ExpressionFieldProps>;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The ambient door for the whole form. Absent (the default), an
|
|
117
|
+
* expression-annotated field renders the PLAIN string input, byte-identically to
|
|
118
|
+
* an unannotated one — the jq authoring door is opt-in, because the dynamic import
|
|
119
|
+
* that would fetch it on demand still EMITS its chunks, worker, and wasm into
|
|
120
|
+
* every consumer that bundles the SDK.
|
|
121
|
+
*
|
|
122
|
+
* A host wires it ONCE above its tree (`SchemaEditor`'s preview and
|
|
123
|
+
* `ElicitationForm` render forms a caller does not own the props of, and a plugin
|
|
124
|
+
* page renders its own); the form's own `expressionField` prop overrides it for a
|
|
125
|
+
* single form.
|
|
126
|
+
*/
|
|
127
|
+
export const ExpressionFieldContext = createContext<ExpressionFieldComponent | undefined>(
|
|
128
|
+
undefined,
|
|
129
|
+
);
|
package/src/schema-form/index.ts
CHANGED
|
@@ -3,11 +3,43 @@
|
|
|
3
3
|
* the tools and interactions features. Public surface: the `SchemaForm`
|
|
4
4
|
* component plus the value/validation helpers a caller needs to seed, validate,
|
|
5
5
|
* and submit.
|
|
6
|
+
*
|
|
7
|
+
* THE FORM-ONLY ENTRY POINT. This module is published as
|
|
8
|
+
* `@tai42/studio-sdk/schema-form` for a consumer that BUNDLES the SDK — a
|
|
9
|
+
* standalone widget rather than a host serving the SDK as a shared module — and
|
|
10
|
+
* wants the form without the barrel: no CSS side effects, and no module graph
|
|
11
|
+
* beyond what the form itself renders.
|
|
12
|
+
*
|
|
13
|
+
* It is also jq-free, and so is the barrel: the visual editor lives in the
|
|
14
|
+
* separately published `@tai42/jq-studio`, which nothing under the SDK imports at
|
|
15
|
+
* runtime. Expression authoring reaches a form only by INJECTION (see
|
|
16
|
+
* `SchemaForm`'s `expressionField` and `ExpressionFieldContext`), so a consumer
|
|
17
|
+
* that never injects a door ships zero jq bytes through either entry point.
|
|
18
|
+
*
|
|
19
|
+
* The design system reaches this entry point through relative imports, so the
|
|
20
|
+
* form renders complete; only the barrel's three CSS side-effect imports stay
|
|
21
|
+
* behind. A consumer importing from here loads the stylesheets it wants
|
|
22
|
+
* explicitly (`@tai42/studio-sdk/tokens.css` and `…/components.css`, plus
|
|
23
|
+
* `…/fonts.css` if it wants the bundled faces).
|
|
24
|
+
*
|
|
25
|
+
* For a BUNDLING consumer only. The shell and every Studio plugin externalise
|
|
26
|
+
* `@tai42/studio-sdk` and resolve it through the served import map, so they must
|
|
27
|
+
* keep importing the barrel: an import map that serves the barrel alone would
|
|
28
|
+
* leave this subpath to be bundled, and the second copy's contexts — the
|
|
29
|
+
* completion provider, the injected expression door — would be different objects
|
|
30
|
+
* from the ones the host provides.
|
|
6
31
|
*/
|
|
7
32
|
export { SchemaForm } from './SchemaForm';
|
|
8
33
|
export type { SchemaFormProps, CompletionProvider } from './SchemaForm';
|
|
9
34
|
export { RecordEntryRendererContext } from './context';
|
|
10
35
|
export type { RecordEntryRenderer, RecordEntryContext } from './context';
|
|
36
|
+
export { ExpressionFieldContext } from './context';
|
|
37
|
+
export type {
|
|
38
|
+
ExpressionFieldComponent,
|
|
39
|
+
ExpressionFieldProps,
|
|
40
|
+
ExpressionInputShape,
|
|
41
|
+
ExpressionInputKey,
|
|
42
|
+
} from './context';
|
|
11
43
|
export { SecretRefField } from './SecretRefField';
|
|
12
44
|
export type { SecretRefFieldProps, SecretRef } from './SecretRefField';
|
|
13
45
|
export { defaultValueForSchema } from './default-value';
|