blockprint 0.0.1 → 0.1.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
@@ -20,6 +20,8 @@ Renders a config against a registry. `config` is `unknown` on purpose — the ou
20
20
 
21
21
  The default fallback renders a warning box in development and `null` in production.
22
22
 
23
+ The optional `wrapBlock={(node, entry, index) => …}` prop wraps every rendered entry — blocks and fallbacks alike — with access to the config entry and its index. This is the hook point for visual-editing overlays, analytics boundaries, or per-block chrome.
24
+
23
25
  ### `parseConfig(input)`
24
26
 
25
27
  The boundary validator, exported for consumers who want to fail at fetch time rather than render time. Throws `ConfigError` with the entry index and reason.
@@ -1,9 +1,15 @@
1
1
  import type { ComponentType, ReactNode } from "react";
2
- import type { BlocksShape, FallbackProps, Registry } from "./types";
2
+ import type { BlockConfig, BlocksShape, FallbackProps, Registry } from "./types";
3
3
  export interface RendererProps<T extends BlocksShape> {
4
4
  /** Untrusted input; the outer shape is validated internally. */
5
5
  config: unknown;
6
6
  registry: Registry<T>;
7
7
  fallback?: ComponentType<FallbackProps>;
8
+ /**
9
+ * Wraps every rendered entry — blocks and fallbacks alike — with access to
10
+ * the config entry and its index. The hook point for visual-editing
11
+ * overlays, analytics boundaries, or per-block chrome.
12
+ */
13
+ wrapBlock?: (node: ReactNode, entry: BlockConfig, index: number) => ReactNode;
8
14
  }
9
- export declare function Renderer<T extends BlocksShape>({ config, registry, fallback, }: RendererProps<T>): ReactNode;
15
+ export declare function Renderer<T extends BlocksShape>({ config, registry, fallback, wrapBlock, }: RendererProps<T>): ReactNode;
package/dist/Renderer.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
2
3
  import { parseConfig } from "./parseConfig";
3
4
  import { DefaultFallback } from "./fallback";
4
5
  // `memo`/`forwardRef` components are exotic objects, not functions, so the
@@ -9,32 +10,37 @@ function resolveDef(def) {
9
10
  }
10
11
  return { component: def };
11
12
  }
12
- export function Renderer({ config, registry, fallback, }) {
13
+ export function Renderer({ config, registry, fallback, wrapBlock, }) {
13
14
  const Fallback = fallback ?? DefaultFallback;
14
15
  const entries = parseConfig(config);
15
16
  return entries.map((entry, index) => {
16
17
  const key = entry.id ?? `${entry.type}-${index}`;
17
- if (!Object.hasOwn(registry.blocks, entry.type)) {
18
- return (_jsx(Fallback, { reason: "unknown-type", type: entry.type, props: entry.props }, key));
18
+ const node = renderEntry(entry, registry, Fallback);
19
+ // The key lives on a Fragment so it survives whatever wrapBlock returns.
20
+ return (_jsx(Fragment, { children: wrapBlock ? wrapBlock(node, entry, index) : node }, key));
21
+ });
22
+ }
23
+ function renderEntry(entry, registry, Fallback) {
24
+ if (!Object.hasOwn(registry.blocks, entry.type)) {
25
+ return (_jsx(Fallback, { reason: "unknown-type", type: entry.type, props: entry.props }));
26
+ }
27
+ const { component: Component, schema } = resolveDef(registry.blocks[entry.type]);
28
+ let props = entry.props;
29
+ if (schema) {
30
+ const result = schema["~standard"].validate(entry.props);
31
+ // React render is synchronous; async schemas are a config mistake and
32
+ // are contained like any other invalid block.
33
+ if (result instanceof Promise) {
34
+ return (_jsx(Fallback, { reason: "invalid-props", type: entry.type, props: entry.props, issues: [
35
+ {
36
+ message: "schema validated asynchronously; blockprint only supports synchronous schemas",
37
+ },
38
+ ] }));
19
39
  }
20
- const { component: Component, schema } = resolveDef(registry.blocks[entry.type]);
21
- let props = entry.props;
22
- if (schema) {
23
- const result = schema["~standard"].validate(entry.props);
24
- // React render is synchronous; async schemas are a config mistake and
25
- // are contained like any other invalid block.
26
- if (result instanceof Promise) {
27
- return (_jsx(Fallback, { reason: "invalid-props", type: entry.type, props: entry.props, issues: [
28
- {
29
- message: "schema validated asynchronously; blockprint only supports synchronous schemas",
30
- },
31
- ] }, key));
32
- }
33
- if (result.issues) {
34
- return (_jsx(Fallback, { reason: "invalid-props", type: entry.type, props: entry.props, issues: result.issues }, key));
35
- }
36
- props = result.value;
40
+ if (result.issues) {
41
+ return (_jsx(Fallback, { reason: "invalid-props", type: entry.type, props: entry.props, issues: result.issues }));
37
42
  }
38
- return _jsx(Component, { ...props }, key);
39
- });
43
+ props = result.value;
44
+ }
45
+ return _jsx(Component, { ...props });
40
46
  }
package/dist/types.d.ts CHANGED
@@ -28,6 +28,15 @@ export interface BlockConfig {
28
28
  props?: unknown;
29
29
  id?: string | number;
30
30
  }
31
+ /**
32
+ * The props a consumer may author for a definition. With a schema this is the
33
+ * schema's INPUT type — authored configs are pre-validation data, so fields
34
+ * with defaults are optional. (The component still receives the parsed
35
+ * output; see PropsOfDef.)
36
+ */
37
+ export type InputOfDef<D> = D extends {
38
+ schema: StandardSchemaV1<infer I, any>;
39
+ } ? I : PropsOfDef<D>;
31
40
  /**
32
41
  * The closed, fully typed config union for a given registry — for configs
33
42
  * authored in TypeScript rather than fetched from a CMS. Block names
@@ -36,7 +45,7 @@ export interface BlockConfig {
36
45
  export type ConfigFor<R extends Registry<BlocksShape>> = R extends Registry<infer T> ? Array<{
37
46
  [K in keyof T]: {
38
47
  type: K;
39
- props: PropsOfDef<T[K]>;
48
+ props: InputOfDef<T[K]>;
40
49
  id?: string | number;
41
50
  };
42
51
  }[keyof T]> : never;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blockprint",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Typed, config-driven block renderer for React. Render arbitrary UI trees from untrusted JSON with compile-time registry safety and runtime schema validation.",
5
5
  "license": "MIT",
6
6
  "repository": {