@acellera/pm-rjsf 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @acellera/pm-rjsf
2
+
3
+ The shared RJSF (Material-UI) renderer for **PlayMolecule v2 manifests** — the
4
+ JSON Schema (`inputSchema`) + `uiSchema` produced by
5
+ [`pmmanifest`](https://github.com/Acellera/pmmanifest). One implementation,
6
+ consumed by both **pmview** and the **pmmanifest dev preview**, so they render
7
+ identically.
8
+
9
+ ## Why a separate package
10
+
11
+ The canonical `inputSchema` stays a faithful Pydantic JSON Schema (it's what the
12
+ backend stages files against, what the SDK reconstructs signatures from, and
13
+ what input validation uses). The RJSF-specific *presentation* lives here:
14
+
15
+ - **AJV 2020 validator** (Pydantic emits 2020-12; RJSF's default is draft-07).
16
+ - **Render-time transforms** (`prepareForRjsf`): collapse `Optional` (`anyOf[X, null]`)
17
+ to a clean field, label discriminated-`oneOf` options from the discriminator,
18
+ and strip platform-managed dirs (`outdir`/`scratchdir`/`execdir`).
19
+ - **MUI templates**: grouped accordions (`ui:options.group`), flat arrays,
20
+ `additionalProperties` maps with custom key labels (`ui:options.keyLabel`),
21
+ `?`-tooltip descriptions, small field titles, hidden discriminators.
22
+
23
+ These are **presentation only** — never mutate the canonical schema with them.
24
+
25
+ ## Use in pmview (Vite)
26
+
27
+ ```bash
28
+ npm install @acellera/pm-rjsf
29
+ ```
30
+
31
+ ```tsx
32
+ import { PMForm } from "@acellera/pm-rjsf";
33
+
34
+ // For a v2 manifest function entry { inputSchema, uiSchema }:
35
+ <PMForm schema={fn.inputSchema} uiSchema={fn.uiSchema} onSubmit={({ formData }) => run(formData)} />
36
+ ```
37
+
38
+ (pmview keeps its existing bespoke renderer for v1 manifests; this is used only
39
+ when `manifestVersion === 2` / an `inputSchema` is present.)
40
+
41
+ ## Use in the pmmanifest preview (no build step)
42
+
43
+ Once published, the preview imports it from a CDN like its other deps:
44
+
45
+ ```js
46
+ import { PMForm } from "https://esm.sh/@acellera/pm-rjsf";
47
+ root.render(React.createElement(PMForm, { schema, uiSchema, onSubmit }));
48
+ ```
49
+
50
+ ## Build
51
+
52
+ ```bash
53
+ npm install
54
+ npm run build # -> dist/pm-rjsf.js (ESM)
55
+ ```
56
+
57
+ React, react-dom, MUI and emotion are **peer dependencies** (the consumer
58
+ provides them); `@rjsf/*` and `ajv` are bundled-as-dependencies.
@@ -0,0 +1,284 @@
1
+ import p from "react";
2
+ import T from "@rjsf/mui";
3
+ import { customizeValidator as v } from "@rjsf/validator-ajv8";
4
+ import A from "ajv/dist/2020";
5
+ import { canExpand as F, ADDITIONAL_PROPERTY_FLAG as j } from "@rjsf/utils";
6
+ function O() {
7
+ return v({
8
+ AjvClass: A,
9
+ customFormats: { path: () => !0 },
10
+ ajvOptionsOverrides: { strict: !1 }
11
+ });
12
+ }
13
+ const h = ["outdir", "scratchdir", "execdir"];
14
+ function C(e) {
15
+ if (!e || !e.properties) return e;
16
+ const r = { ...e.properties };
17
+ for (const t of h) delete r[t];
18
+ const n = (e.required || []).filter((t) => !h.includes(t));
19
+ return { ...e, properties: r, required: n };
20
+ }
21
+ function s(e) {
22
+ if (Array.isArray(e)) return e.map(s);
23
+ if (e && typeof e == "object") {
24
+ if (Array.isArray(e.anyOf)) {
25
+ const n = e.anyOf.filter((t) => !(t && t.type === "null"));
26
+ if (n.length === 1) {
27
+ const { anyOf: t, ...a } = e;
28
+ return s({ ...n[0], ...a });
29
+ }
30
+ e = { ...e, anyOf: n };
31
+ }
32
+ if (Array.isArray(e.oneOf) && e.discriminator && e.discriminator.mapping) {
33
+ const n = {};
34
+ for (const [t, a] of Object.entries(e.discriminator.mapping))
35
+ n[a.split("/").pop()] = t;
36
+ e = {
37
+ ...e,
38
+ oneOf: e.oneOf.map((t) => {
39
+ const a = t && t.$ref ? t.$ref.split("/").pop() : null;
40
+ return a && n[a] && !t.title ? { ...t, title: n[a] } : t;
41
+ })
42
+ };
43
+ }
44
+ const r = {};
45
+ for (const [n, t] of Object.entries(e)) r[n] = s(t);
46
+ return r;
47
+ }
48
+ return e;
49
+ }
50
+ function B(e) {
51
+ return s(C(e));
52
+ }
53
+ const i = p.createElement;
54
+ function I(e) {
55
+ const r = e.uiSchema || {}, n = e.registry.templates, t = [], a = /* @__PURE__ */ new Map();
56
+ for (const l of e.properties) {
57
+ const c = r[l.name] && r[l.name]["ui:options"] && r[l.name]["ui:options"].group;
58
+ c ? (a.get(c) || a.set(c, []).get(c)).push(l) : t.push(l);
59
+ }
60
+ const d = [];
61
+ e.title && d.push(
62
+ i(n.TitleFieldTemplate, {
63
+ key: "__title",
64
+ id: e.idSchema.$id + "__title",
65
+ title: e.title,
66
+ required: e.required,
67
+ schema: e.schema,
68
+ uiSchema: e.uiSchema,
69
+ registry: e.registry
70
+ })
71
+ ), e.description && d.push(
72
+ i(n.DescriptionFieldTemplate, {
73
+ key: "__desc",
74
+ id: e.idSchema.$id + "__description",
75
+ description: e.description,
76
+ schema: e.schema,
77
+ uiSchema: e.uiSchema,
78
+ registry: e.registry
79
+ })
80
+ );
81
+ for (const l of t)
82
+ d.push(i("div", { key: l.name, className: "pm-field" }, l.content));
83
+ for (const [l, c] of a)
84
+ d.push(
85
+ i(
86
+ "details",
87
+ { key: l, className: "pm-group" },
88
+ i("summary", null, l),
89
+ i(
90
+ "div",
91
+ { className: "pm-body" },
92
+ c.map((m) => i("div", { key: m.name, className: "pm-field" }, m.content))
93
+ )
94
+ )
95
+ );
96
+ return F(e.schema, e.uiSchema, e.formData) && d.push(
97
+ i(n.ButtonTemplates.AddButton, {
98
+ key: "__add",
99
+ className: "object-property-expand",
100
+ onClick: e.onAddClick(e.schema),
101
+ disabled: e.disabled || e.readonly,
102
+ uiSchema: e.uiSchema,
103
+ registry: e.registry
104
+ })
105
+ ), i("div", null, d);
106
+ }
107
+ function R(e) {
108
+ const {
109
+ children: r,
110
+ classNames: n,
111
+ style: t,
112
+ disabled: a,
113
+ id: d,
114
+ label: l,
115
+ onKeyChange: c,
116
+ onDropPropertyClick: m,
117
+ readonly: u,
118
+ schema: b,
119
+ uiSchema: o,
120
+ registry: f
121
+ } = e;
122
+ if (!(j in b))
123
+ return i("div", { className: n, style: t }, r);
124
+ const S = f.templates.ButtonTemplates.RemoveButton, k = o && o["ui:options"] && o["ui:options"].keyLabel || l + " Key", y = d + "-key", _ = {
125
+ width: "100%",
126
+ padding: ".5rem",
127
+ border: "1px solid #ccc",
128
+ borderRadius: "4px",
129
+ font: "inherit",
130
+ boxSizing: "border-box"
131
+ };
132
+ return i(
133
+ "div",
134
+ { style: { display: "flex", gap: "1rem", alignItems: "flex-start", marginBottom: ".75rem" } },
135
+ i(
136
+ "div",
137
+ { style: { flex: "0 0 30%" } },
138
+ i("label", { htmlFor: y, style: { display: "block", marginBottom: ".25rem" } }, k),
139
+ i("input", {
140
+ id: y,
141
+ type: "text",
142
+ defaultValue: l,
143
+ disabled: a || u,
144
+ style: _,
145
+ onBlur: (x) => c(x.target.value)
146
+ })
147
+ ),
148
+ i("div", { style: { flex: "1 1 auto" } }, r),
149
+ i(
150
+ "div",
151
+ { style: { flex: "0 0 auto", paddingTop: "1.6rem" } },
152
+ i(S, {
153
+ disabled: a || u,
154
+ onClick: m(l),
155
+ uiSchema: o,
156
+ registry: f
157
+ })
158
+ )
159
+ );
160
+ }
161
+ function D(e) {
162
+ const r = e.schema && e.schema.description || (typeof e.description == "string" ? e.description : "");
163
+ if (!r) return null;
164
+ const n = String(r);
165
+ return i(
166
+ "span",
167
+ {
168
+ title: n,
169
+ "aria-label": n,
170
+ style: {
171
+ display: "inline-flex",
172
+ alignItems: "center",
173
+ justifyContent: "center",
174
+ width: "18px",
175
+ height: "18px",
176
+ borderRadius: "50%",
177
+ border: "1px solid #999",
178
+ color: "#666",
179
+ fontSize: "12px",
180
+ lineHeight: 1,
181
+ cursor: "help",
182
+ marginLeft: "6px",
183
+ verticalAlign: "middle"
184
+ }
185
+ },
186
+ "?"
187
+ );
188
+ }
189
+ function N(e) {
190
+ return e.title ? i(
191
+ "div",
192
+ { style: { fontSize: ".9rem", fontWeight: 600, margin: ".4rem 0 .3rem" } },
193
+ e.title,
194
+ e.required ? i("span", { style: { color: "#b00" } }, " *") : null
195
+ ) : null;
196
+ }
197
+ function w(e) {
198
+ const r = e.registry.templates, n = r.ArrayFieldItemTemplate, t = e.uiSchema && e.uiSchema["ui:title"] || e.title, a = [];
199
+ t && a.push(
200
+ i(r.TitleFieldTemplate, {
201
+ key: "__t",
202
+ id: e.idSchema.$id + "__title",
203
+ title: t,
204
+ required: e.required,
205
+ schema: e.schema,
206
+ uiSchema: e.uiSchema,
207
+ registry: e.registry
208
+ })
209
+ );
210
+ const d = e.schema && e.schema.description;
211
+ return d && a.push(
212
+ i(r.DescriptionFieldTemplate, {
213
+ key: "__d",
214
+ id: e.idSchema.$id + "__description",
215
+ description: d,
216
+ schema: e.schema,
217
+ uiSchema: e.uiSchema,
218
+ registry: e.registry
219
+ })
220
+ ), (e.items || []).forEach(
221
+ ({ key: l, ...c }) => a.push(i(n, { key: l, ...c }))
222
+ ), e.canAdd && a.push(
223
+ i(r.ButtonTemplates.AddButton, {
224
+ key: "__add",
225
+ onClick: e.onAddClick,
226
+ disabled: e.disabled || e.readonly,
227
+ uiSchema: e.uiSchema,
228
+ registry: e.registry
229
+ })
230
+ ), i("div", null, a);
231
+ }
232
+ const q = {
233
+ ObjectFieldTemplate: I,
234
+ WrapIfAdditionalTemplate: R,
235
+ DescriptionFieldTemplate: D,
236
+ TitleFieldTemplate: N,
237
+ ArrayFieldTemplate: w
238
+ }, E = `
239
+ .pm-field { margin: .5rem 0; }
240
+ details.pm-group { border: 1px solid #e0e0e0; border-radius: 4px; margin: .75rem 0; }
241
+ details.pm-group > summary {
242
+ list-style: none; cursor: pointer; padding: .6rem .9rem; font-size: .9rem;
243
+ font-weight: 600; display: flex; justify-content: space-between; align-items: center;
244
+ }
245
+ details.pm-group > summary::-webkit-details-marker { display: none; }
246
+ details.pm-group > summary::after { content: "\\25be"; color: #888; font-weight: 400; }
247
+ details.pm-group[open] > summary::after { content: "\\25b4"; }
248
+ details.pm-group > .pm-body { padding: .25rem .9rem .6rem; }
249
+ `;
250
+ let g = !1;
251
+ function L() {
252
+ if (g || typeof document > "u") return;
253
+ g = !0;
254
+ const e = document.createElement("style");
255
+ e.setAttribute("data-pm-rjsf", ""), e.textContent = E, document.head.appendChild(e);
256
+ }
257
+ const P = O();
258
+ function K({ schema: e, uiSchema: r, ...n }) {
259
+ L();
260
+ const t = B(e);
261
+ return p.createElement(T, {
262
+ schema: t,
263
+ uiSchema: r,
264
+ validator: P,
265
+ templates: q,
266
+ ...n
267
+ });
268
+ }
269
+ export {
270
+ w as ArrayFieldTemplate,
271
+ D as DescriptionFieldTemplate,
272
+ I as GroupedObjectFieldTemplate,
273
+ K as PMForm,
274
+ E as PM_RJSF_CSS,
275
+ h as SPECIAL_DIRS,
276
+ N as TitleFieldTemplate,
277
+ R as WrapIfAdditionalTemplate,
278
+ L as injectStyles,
279
+ O as makeValidator,
280
+ B as prepareForRjsf,
281
+ s as simplify,
282
+ C as stripSpecialDirs,
283
+ q as templates
284
+ };
package/index.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { ComponentType } from "react";
2
+
3
+ export interface PMFormProps {
4
+ /** Canonical inputSchema (JSON Schema 2020-12, as emitted by pmmanifest). */
5
+ schema: Record<string, unknown>;
6
+ /** uiSchema (RJSF), as emitted by pmmanifest. */
7
+ uiSchema?: Record<string, unknown>;
8
+ /** Called with { formData } on submit. */
9
+ onSubmit?: (data: { formData: unknown }) => void;
10
+ formData?: unknown;
11
+ disabled?: boolean;
12
+ readonly?: boolean;
13
+ [key: string]: unknown;
14
+ }
15
+
16
+ /** Drop-in RJSF (MUI) form for a PlayMolecule v2 manifest function entry. */
17
+ export const PMForm: ComponentType<PMFormProps>;
18
+
19
+ /** Build the AJV-2020 validator used by PMForm (Pydantic emits 2020-12). */
20
+ export function makeValidator(): unknown;
21
+
22
+ /** Render-time schema prep: strip platform dirs + collapse Optional anyOf + title oneOf options. */
23
+ export function prepareForRjsf<T>(schema: T): T;
24
+ export function simplify<T>(schema: T): T;
25
+ export function stripSpecialDirs<T>(schema: T): T;
26
+ export const SPECIAL_DIRS: string[];
27
+
28
+ export const templates: Record<string, ComponentType<any>>;
29
+ export const GroupedObjectFieldTemplate: ComponentType<any>;
30
+ export const WrapIfAdditionalTemplate: ComponentType<any>;
31
+ export const DescriptionFieldTemplate: ComponentType<any>;
32
+ export const TitleFieldTemplate: ComponentType<any>;
33
+ export const ArrayFieldTemplate: ComponentType<any>;
34
+
35
+ export const PM_RJSF_CSS: string;
36
+ export function injectStyles(): void;
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@acellera/pm-rjsf",
3
+ "version": "0.0.1",
4
+ "description": "Shared RJSF renderer for PlayMolecule v2 (JSON Schema) manifests — used by pmview and the pmmanifest preview.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Acellera/pm-rjsf.git"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "index.d.ts"
14
+ ],
15
+ "main": "./dist/pm-rjsf.js",
16
+ "module": "./dist/pm-rjsf.js",
17
+ "types": "./index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./index.d.ts",
21
+ "import": "./dist/pm-rjsf.js"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "vite build",
26
+ "dev": "vite build --watch"
27
+ },
28
+ "dependencies": {
29
+ "@rjsf/core": "^5",
30
+ "@rjsf/mui": "^5",
31
+ "@rjsf/utils": "^5",
32
+ "@rjsf/validator-ajv8": "^5",
33
+ "ajv": "^8.12.0"
34
+ },
35
+ "peerDependencies": {
36
+ "@emotion/react": "^11",
37
+ "@emotion/styled": "^11",
38
+ "@mui/icons-material": "^5",
39
+ "@mui/material": "^5",
40
+ "react": "^18",
41
+ "react-dom": "^18"
42
+ },
43
+ "devDependencies": {
44
+ "vite": "^7"
45
+ }
46
+ }