@persona/layout-editor-remote-module 0.10.0 → 0.11.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 CHANGED
@@ -180,3 +180,24 @@ const { modules } = parseRemoteModuleManifest(await response.json());
180
180
  It throws `RemoteModuleManifestParseError` (with `.issues` and a prettified `.message`) on a
181
181
  malformed document; `safeParseRemoteModuleManifest` returns `{ success, data | error }` instead of
182
182
  throwing.
183
+
184
+ ### Rendering a config panel
185
+
186
+ A host stores a placed module's config under a `config` property, so its RJSF form edits
187
+ `{ config }`. The `./config-form` entry adapts a manifest to that shape, with no React or RJSF
188
+ dependency:
189
+
190
+ ```ts
191
+ import {
192
+ nestRemoteConfigSchema,
193
+ nestRemoteUiConfigSchema,
194
+ remoteConfigDefaults,
195
+ } from '@persona/layout-editor-remote-module/config-form';
196
+
197
+ const schema = nestRemoteConfigSchema(module.configSchema); // `$ref`s rewritten to match
198
+ const uiSchema = nestRemoteUiConfigSchema(module.uiConfigSchema);
199
+ const initialConfig = remoteConfigDefaults(module.configSchema); // top-level `default`s
200
+ ```
201
+
202
+ Render the form with `schema`, `uiSchema` and `formData: { config }`, and read the edited config
203
+ back from `formData.config`.
@@ -1,4 +1,5 @@
1
- import { n as Json, t as RemoteModuleContext } from "./protocol-Dqnl8JTs.mjs";
1
+ import { t as Json } from "./types-CBSiNsDJ.mjs";
2
+ import { t as RemoteModuleContext } from "./protocol-ClWDYwW2.mjs";
2
3
  import { ReactNode } from "react";
3
4
 
4
5
  //#region src/bridge/origin.d.ts
@@ -1,4 +1,4 @@
1
- import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-Br3C6FM7.mjs";
1
+ import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-BfjTaW1S.mjs";
2
2
  import { Component, useEffect, useRef, useState } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
@@ -1,4 +1,5 @@
1
- import { n as Json, t as RemoteModuleContext } from "./protocol-Dqnl8JTs.mjs";
1
+ import { t as Json } from "./types-CBSiNsDJ.mjs";
2
+ import { t as RemoteModuleContext } from "./protocol-ClWDYwW2.mjs";
2
3
  import { CSSProperties, ReactNode, Ref } from "react";
3
4
 
4
5
  //#region src/bridge/host/buildModuleSrc.d.ts
@@ -1,4 +1,4 @@
1
- import { a as assertNotSelfOrigin, i as parseModuleToHostMessage, n as SOURCE, t as PROTOCOL_VERSION } from "./protocol-Br3C6FM7.mjs";
1
+ import { a as assertNotSelfOrigin, i as parseModuleToHostMessage, n as SOURCE, t as PROTOCOL_VERSION } from "./protocol-BfjTaW1S.mjs";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
 
@@ -0,0 +1,23 @@
1
+ import { n as JsonSchemaObject, t as Json } from "./types-CBSiNsDJ.mjs";
2
+
3
+ //#region src/config-form.d.ts
4
+
5
+ /** JSON pointer, in the nested schema, of the module's own config schema. */
6
+ declare const REMOTE_CONFIG_POINTER_PREFIX = "#/properties/config";
7
+ /**
8
+ * Nest a manifest `configSchema` under a top-level `config` property, rewriting `$ref`s so they
9
+ * still resolve. Drops `$schema` and `$id`; keeps `$defs` / `definitions` at the root.
10
+ */
11
+ declare function nestRemoteConfigSchema(configSchema: JsonSchemaObject): JsonSchemaObject;
12
+ /**
13
+ * Nest a manifest `uiConfigSchema` to match {@link nestRemoteConfigSchema}, hiding the wrapper
14
+ * object's title so the module's fields render as if top level.
15
+ */
16
+ declare function nestRemoteUiConfigSchema(uiConfigSchema: JsonSchemaObject | undefined): JsonSchemaObject;
17
+ /**
18
+ * A config holding each top-level `configSchema` property's `default`. Properties without one are
19
+ * left unset; defaults are cloned so the result can be mutated freely.
20
+ */
21
+ declare function remoteConfigDefaults(configSchema: JsonSchemaObject | undefined): Record<string, Json>;
22
+ //#endregion
23
+ export { REMOTE_CONFIG_POINTER_PREFIX, nestRemoteConfigSchema, nestRemoteUiConfigSchema, remoteConfigDefaults };
@@ -0,0 +1,79 @@
1
+ //#region src/config-form.ts
2
+ /** JSON pointer, in the nested schema, of the module's own config schema. */
3
+ const REMOTE_CONFIG_POINTER_PREFIX = "#/properties/config";
4
+ /** Keywords whose values are instance data, never schemas, so a `$ref` key inside them is data. */
5
+ const LITERAL_KEYWORDS = new Set([
6
+ "const",
7
+ "default",
8
+ "enum",
9
+ "examples"
10
+ ]);
11
+ /** Keywords whose values map arbitrary names to schemas, so a name like `default` is a schema. */
12
+ const SCHEMA_MAP_KEYWORDS = new Set([
13
+ "$defs",
14
+ "definitions",
15
+ "dependentSchemas",
16
+ "patternProperties",
17
+ "properties"
18
+ ]);
19
+ function rewriteSchemaMap(value, schemaId) {
20
+ if (value == null || typeof value !== "object" || Array.isArray(value)) return value;
21
+ return Object.fromEntries(Object.entries(value).map(([name, schema]) => [name, rewriteReferences(schema, schemaId)]));
22
+ }
23
+ function rewriteReferences(value, schemaId) {
24
+ if (Array.isArray(value)) return value.map((item) => rewriteReferences(item, schemaId));
25
+ if (value == null || typeof value !== "object") return value;
26
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => {
27
+ if (key === "$ref" && typeof child === "string") return [key, rewriteReference(child, schemaId)];
28
+ if (LITERAL_KEYWORDS.has(key)) return [key, child];
29
+ if (SCHEMA_MAP_KEYWORDS.has(key)) return [key, rewriteSchemaMap(child, schemaId)];
30
+ return [key, rewriteReferences(child, schemaId)];
31
+ }));
32
+ }
33
+ function rewriteReference(reference, schemaId) {
34
+ if (schemaId != null) {
35
+ if (reference === schemaId) return REMOTE_CONFIG_POINTER_PREFIX;
36
+ if (reference.startsWith(`${schemaId}#`)) return rewriteReference(reference.slice(schemaId.length));
37
+ }
38
+ if (reference === "#") return REMOTE_CONFIG_POINTER_PREFIX;
39
+ if (reference.startsWith("#/$defs/") || reference.startsWith("#/definitions/")) return reference;
40
+ if (reference.startsWith("#/")) return `${REMOTE_CONFIG_POINTER_PREFIX}${reference.slice(1)}`;
41
+ return reference;
42
+ }
43
+ /**
44
+ * Nest a manifest `configSchema` under a top-level `config` property, rewriting `$ref`s so they
45
+ * still resolve. Drops `$schema` and `$id`; keeps `$defs` / `definitions` at the root.
46
+ */
47
+ function nestRemoteConfigSchema(configSchema) {
48
+ const { $defs, definitions, $id, ...rest } = configSchema;
49
+ const body = Object.fromEntries(Object.entries(rest).filter(([key]) => key !== "$schema"));
50
+ const schemaId = typeof $id === "string" ? $id : void 0;
51
+ return {
52
+ type: "object",
53
+ properties: { config: rewriteReferences(body, schemaId) },
54
+ ...$defs != null && { $defs: rewriteSchemaMap($defs, schemaId) },
55
+ ...definitions != null && { definitions: rewriteSchemaMap(definitions, schemaId) }
56
+ };
57
+ }
58
+ /**
59
+ * Nest a manifest `uiConfigSchema` to match {@link nestRemoteConfigSchema}, hiding the wrapper
60
+ * object's title so the module's fields render as if top level.
61
+ */
62
+ function nestRemoteUiConfigSchema(uiConfigSchema) {
63
+ return { config: {
64
+ ...uiConfigSchema,
65
+ "ui:title": ""
66
+ } };
67
+ }
68
+ /**
69
+ * A config holding each top-level `configSchema` property's `default`. Properties without one are
70
+ * left unset; defaults are cloned so the result can be mutated freely.
71
+ */
72
+ function remoteConfigDefaults(configSchema) {
73
+ const properties = configSchema?.properties;
74
+ if (properties == null || typeof properties !== "object" || Array.isArray(properties)) return {};
75
+ return Object.fromEntries(Object.entries(properties).filter(([, propertySchema]) => typeof propertySchema === "object" && propertySchema != null && !Array.isArray(propertySchema) && "default" in propertySchema).map(([key, propertySchema]) => [key, structuredClone(propertySchema.default)]));
76
+ }
77
+
78
+ //#endregion
79
+ export { REMOTE_CONFIG_POINTER_PREFIX, nestRemoteConfigSchema, nestRemoteUiConfigSchema, remoteConfigDefaults };
@@ -1,14 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region src/types.d.ts
4
-
5
- type Json = boolean | JsonObject | JsonValue[] | null | number | string;
6
- type JsonObject = {
7
- [key: string]: Json;
8
- };
9
- type JsonValue = Json;
10
- //#endregion
11
3
  //#region src/bridge/protocol.d.ts
4
+
12
5
  /**
13
6
  * Runtime facts about where the module is rendered, owned by the host and not
14
7
  * derivable by the module itself. Distinct from `data`, which is the module's
@@ -24,4 +17,4 @@ declare const remoteModuleContextSchema: z.ZodObject<{
24
17
  }, z.core.$strip>;
25
18
  type RemoteModuleContext = z.infer<typeof remoteModuleContextSchema>;
26
19
  //#endregion
27
- export { Json as n, RemoteModuleContext as t };
20
+ export { RemoteModuleContext as t };
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/types.d.ts
4
+
5
+ type Json = boolean | JsonObject | JsonValue[] | null | number | string;
6
+ /**
7
+ * Loose JSON Schema object. Deliberately not a full JSON Schema type system —
8
+ * this package only needs to carry what `z.toJSONSchema()` emits for consumers
9
+ * to validate or render.
10
+ */
11
+ type JsonSchemaObject = Record<string, unknown>;
12
+ type JsonObject = {
13
+ [key: string]: Json;
14
+ };
15
+ type JsonValue = Json;
16
+ //#endregion
17
+ export { JsonSchemaObject as n, Json as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persona/layout-editor-remote-module",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Shared manifest format, Vite plugin, and host/iframe bridge for remotely-defined dashboard layout modules in Persona dashboard apps.",
5
5
  "license": "SEE LICENSE",
6
6
  "type": "module",
@@ -15,6 +15,9 @@
15
15
  ],
16
16
  "bridge-frame": [
17
17
  "dist/bridge-frame.d.mts"
18
+ ],
19
+ "config-form": [
20
+ "dist/config-form.d.mts"
18
21
  ]
19
22
  }
20
23
  },
@@ -111,6 +114,10 @@
111
114
  "types": "./dist/bridge-host.d.mts",
112
115
  "import": "./dist/bridge-host.mjs"
113
116
  },
117
+ "./config-form": {
118
+ "types": "./dist/config-form.d.mts",
119
+ "import": "./dist/config-form.mjs"
120
+ },
114
121
  "./bridge-frame": {
115
122
  "types": "./dist/bridge-frame.d.mts",
116
123
  "import": "./dist/bridge-frame.mjs"