@openeditor/custom-block 0.0.46 → 0.0.48

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
@@ -1,60 +1,42 @@
1
- # OpenEditor custom blocks
1
+ # `@openeditor/custom-block`
2
2
 
3
- `@openeditor/custom-block` defines the only public custom-block interface. Every installed block uses the fixed `customBlock` document node. Installation does not change the ProseMirror schema.
3
+ Use this package to add trusted, build-time custom blocks to OpenEditor without adding a ProseMirror node type for each block.
4
4
 
5
- Use the headless entry point for the portable definition, registry, validation manifest, migrations, and static export. Import authoring code from `@openeditor/custom-block/editor`. Import published Viewer code from `@openeditor/custom-block/viewer`. The Viewer entry point does not import Tiptap or editor code.
5
+ OpenEditor stores every installed block in one atomic `customBlock` node. A block package owns its data parser, version migration, static output, and asset references. OpenEditor owns the envelope, registry, editor and Viewer lifecycle, missing-block fallback, and safe HTML serialization.
6
6
 
7
7
  ```ts
8
- import { defineOpenEditorCustomBlock } from "@openeditor/custom-block";
8
+ import {
9
+ createOpenEditorCustomBlockRegistry,
10
+ defineOpenEditorCustomBlock,
11
+ } from "@openeditor/custom-block";
9
12
 
10
- export const card = defineOpenEditorCustomBlock({
13
+ type CardData = { title: string };
14
+
15
+ export const card = defineOpenEditorCustomBlock<CardData>({
11
16
  id: "acme.card",
12
17
  label: "Card",
13
18
  version: 1,
14
- dataSchema: {
15
- type: "object",
16
- properties: { title: { type: "string", minLength: 1 } },
17
- required: ["title"],
18
- additionalProperties: false,
19
+ createData: () => ({ title: "Untitled" }),
20
+ parseData: (value) => {
21
+ if (!value || typeof value !== "object" || !("title" in value))
22
+ throw new Error("Card title is required.");
23
+ if (typeof value.title !== "string")
24
+ throw new Error("Card title must be a string.");
25
+ return { title: value.title };
19
26
  },
20
- initialData: () => ({ title: "Untitled" }),
21
27
  toHtml: ({ data }) => ({ tag: "article", children: [data.title] }),
22
28
  toText: ({ data }) => data.title,
23
29
  });
24
- ```
25
-
26
- Static HTML uses an OpenEditor-owned safe tree. It does not accept raw HTML. Use the general `{ type: "document" }` data schema and the Editor and Viewer `host.fields.document` adapters for nested rich documents. Use `host.resolveUrl`, `host.links`, `host.icons`, and `host.assets` for product-neutral host facilities.
27
-
28
- Define authoring and published Viewer adapters in separate files:
29
-
30
- ```tsx
31
- // card-editor.tsx
32
- import { defineOpenEditorCustomBlockEditor } from "@openeditor/custom-block/editor";
33
- import { card } from "./card";
34
30
 
35
- export const cardEditor = defineOpenEditorCustomBlockEditor({
36
- block: card,
37
- render: ({ data, updateData }) => (
38
- <input value={data.title} onChange={(event) => updateData({ title: event.target.value })} />
39
- ),
40
- });
31
+ export const registry = createOpenEditorCustomBlockRegistry([card]);
41
32
  ```
42
33
 
43
- ```tsx
44
- // card-viewer.tsx
45
- import { defineOpenEditorCustomBlockViewer } from "@openeditor/custom-block/viewer";
46
- import { card } from "./card";
47
-
48
- export const cardViewer = defineOpenEditorCustomBlockViewer({
49
- block: card,
50
- render: ({ data }) => <article>{data.title}</article>,
51
- });
52
- ```
34
+ The parser is the authoritative data contract. Use the same registry in the browser, backend, publisher, migration, and exporters. This prevents validation rules from drifting across environments.
53
35
 
54
- Create one registry from headless definitions. Pass Editor adapters only to authoring surfaces and Viewer adapters only to `@openeditor/react/viewer`. The host provides `fields.document`, safe URL resolution, optional navigation, icon catalog, and managed raster assets. A Viewer adapter cannot update saved data.
36
+ Import React editor adapters from `@openeditor/custom-block/editor`. Import published Viewer adapters from `@openeditor/custom-block/viewer`. The Viewer entry point does not import Tiptap or editor code.
55
37
 
56
- Use integer-keyed `migrations` to convert stored data to the current version. Missing, disabled, incompatible, and invalid blocks preserve the original node and return an explicit registry resolution status. Backends validate `registry.manifests` with `validateOpenEditorCustomBlockEnvelope`; manifests contain JSON only and do not execute extension code. Use `conformOpenEditorCustomBlock(definition)` in each block package test to verify initial data, manifest validation, and safe static output.
38
+ Use `migrate` only while a stored data version is active. The function must return the complete next envelope and increase the version. A final application release can remove the migration after all stored documents use the current version.
57
39
 
58
- Use `{ type: "string", format: "asset-id" }` for a host-managed asset reference. Asset IDs are opaque, have a maximum of 128 characters, and cannot contain URLs, paths, whitespace, or control characters. A backend can call `extractOpenEditorCustomBlockAssetReferences(envelope, manifests)` to authorize all asset references, including references in nested OpenEditor document fields, without loading block code.
40
+ Use `assets` to return host-managed asset IDs and JSON paths. The host authorizes and resolves these opaque IDs. Block packages do not receive storage clients or credentials.
59
41
 
60
- Portable `constraints` cover primitive and keyed uniqueness, scoped map keys, references, acyclic graphs, and conditional URL policies. For example, `keysIn` can validate each directory row against the column IDs in the same directory. These constraints are JSON data in the manifest and run in backend validation.
42
+ Use `conformOpenEditorCustomBlock` in each block package test. It verifies initial data, parsing, and safe static output.
package/dist/editor.d.ts CHANGED
@@ -26,6 +26,14 @@ type OpenEditorCustomBlockEditorAdapter<TData extends OpenEditorCustomBlockData
26
26
  render: ComponentType<OpenEditorCustomBlockEditorContext<TData>>;
27
27
  };
28
28
  declare const defineOpenEditorCustomBlockEditor: <TData extends OpenEditorCustomBlockData>(adapter: OpenEditorCustomBlockEditorAdapter<TData>) => OpenEditorCustomBlockEditorAdapter<TData>;
29
+ type OpenEditorCustomBlockEvent = {
30
+ type: string;
31
+ button?: unknown;
32
+ };
33
+ declare const shouldStopOpenEditorCustomBlockEvent: (event: OpenEditorCustomBlockEvent) => boolean;
34
+ declare const stopOpenEditorCustomBlockEventPropagation: (event: OpenEditorCustomBlockEvent & {
35
+ stopPropagation: () => void;
36
+ }) => void;
29
37
  type OpenEditorCustomBlockEditorSurfaceProps = {
30
38
  node: ProseMirrorNode;
31
39
  registry: OpenEditorCustomBlockRegistry;
@@ -46,4 +54,4 @@ declare const createOpenEditorCustomBlockEditorExtension: (options: {
46
54
  host: OpenEditorCustomBlockEditorHost;
47
55
  }) => Node<any, any>;
48
56
 
49
- export { type OpenEditorCustomBlockDocumentEditorProps, type OpenEditorCustomBlockEditorAdapter, type OpenEditorCustomBlockEditorContext, type OpenEditorCustomBlockEditorHost, OpenEditorCustomBlockEditorSurface, type OpenEditorCustomBlockEditorSurfaceProps, createOpenEditorCustomBlockEditorExtension, defineOpenEditorCustomBlockEditor };
57
+ export { type OpenEditorCustomBlockDocumentEditorProps, type OpenEditorCustomBlockEditorAdapter, type OpenEditorCustomBlockEditorContext, type OpenEditorCustomBlockEditorHost, OpenEditorCustomBlockEditorSurface, type OpenEditorCustomBlockEditorSurfaceProps, createOpenEditorCustomBlockEditorExtension, defineOpenEditorCustomBlockEditor, shouldStopOpenEditorCustomBlockEvent, stopOpenEditorCustomBlockEventPropagation };
package/dist/editor.js CHANGED
@@ -5,6 +5,14 @@ import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
6
6
  // src/editor.tsx
7
7
  var defineOpenEditorCustomBlockEditor = (adapter) => adapter;
8
+ var shouldStopOpenEditorCustomBlockEvent = (event) => {
9
+ if (event.type === "contextmenu") return true;
10
+ if (event.type !== "mousedown" && event.type !== "pointerdown") return false;
11
+ return "button" in event && typeof event.button === "number" && event.button !== 0;
12
+ };
13
+ var stopOpenEditorCustomBlockEventPropagation = (event) => {
14
+ if (shouldStopOpenEditorCustomBlockEvent(event)) event.stopPropagation();
15
+ };
8
16
  var EditorAdapterErrorBoundary = class extends Component {
9
17
  state = { failed: false };
10
18
  static getDerivedStateFromError() {
@@ -66,20 +74,29 @@ var createOpenEditorCustomBlockEditorExtension = (options) => Node.create({
66
74
  addAttributes: () => ({ "openeditor-id": { default: null }, blockId: { default: null }, version: { default: null }, data: { default: {} } }),
67
75
  renderHTML: ({ node }) => ["div", { "data-openeditor-custom-block": node.attrs.blockId ?? "unknown" }],
68
76
  addNodeView() {
69
- return ReactNodeViewRenderer((props) => /* @__PURE__ */ jsx(NodeViewWrapper, { "data-openeditor-custom-block": props.node.attrs.blockId ?? "unknown", children: /* @__PURE__ */ jsx(
70
- OpenEditorCustomBlockEditorSurface,
77
+ return ReactNodeViewRenderer((props) => /* @__PURE__ */ jsx(
78
+ NodeViewWrapper,
71
79
  {
72
- adapters: options.adapters,
73
- host: options.host,
74
- node: props.node.toJSON(),
75
- registry: options.registry,
76
- selected: props.selected,
77
- updateNode: (next) => props.updateAttributes(next.attrs ?? {})
80
+ "data-openeditor-custom-block": props.node.attrs.blockId ?? "unknown",
81
+ onContextMenu: stopOpenEditorCustomBlockEventPropagation,
82
+ onMouseDown: stopOpenEditorCustomBlockEventPropagation,
83
+ onPointerDown: stopOpenEditorCustomBlockEventPropagation,
84
+ children: /* @__PURE__ */ jsx(
85
+ OpenEditorCustomBlockEditorSurface,
86
+ {
87
+ adapters: options.adapters,
88
+ host: options.host,
89
+ node: props.node.toJSON(),
90
+ registry: options.registry,
91
+ selected: props.selected,
92
+ updateNode: (next) => props.updateAttributes(next.attrs ?? {})
93
+ }
94
+ )
78
95
  }
79
- ) }));
96
+ ));
80
97
  }
81
98
  });
82
99
 
83
- export { OpenEditorCustomBlockEditorSurface, createOpenEditorCustomBlockEditorExtension, defineOpenEditorCustomBlockEditor };
100
+ export { OpenEditorCustomBlockEditorSurface, createOpenEditorCustomBlockEditorExtension, defineOpenEditorCustomBlockEditor, shouldStopOpenEditorCustomBlockEvent, stopOpenEditorCustomBlockEventPropagation };
84
101
  //# sourceMappingURL=editor.js.map
85
102
  //# sourceMappingURL=editor.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/editor.tsx"],"names":["adapter"],"mappings":"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","file":"editor.js","sourcesContent":["import type { OpenEditorDocument, ProseMirrorNode } from \"@openeditor/core\";\nimport {\n type OpenEditorCustomBlockData,\n type OpenEditorCustomBlockDefinition,\n type OpenEditorCustomBlockHost,\n type OpenEditorCustomBlockRegistry,\n} from \"./index\";\nimport { Component, useEffect, useRef, type ComponentType, type ReactNode } from \"react\";\nimport { Node } from \"@tiptap/core\";\nimport { NodeViewWrapper, ReactNodeViewRenderer, type ReactNodeViewProps } from \"@tiptap/react\";\n\nexport type OpenEditorCustomBlockDocumentEditorProps = {\n value: OpenEditorDocument;\n onChange: (value: OpenEditorDocument) => void;\n ariaLabel: string;\n};\nexport type OpenEditorCustomBlockEditorHost = OpenEditorCustomBlockHost & {\n fields: { document: ComponentType<OpenEditorCustomBlockDocumentEditorProps> };\n};\nexport type OpenEditorCustomBlockEditorContext<TData extends OpenEditorCustomBlockData> = {\n data: Readonly<TData>;\n instanceId: string;\n host: OpenEditorCustomBlockEditorHost;\n updateData: (update: TData | ((current: Readonly<TData>) => TData)) => void;\n selected: boolean;\n};\nexport type OpenEditorCustomBlockEditorAdapter<TData extends OpenEditorCustomBlockData = any> = {\n block: OpenEditorCustomBlockDefinition<TData>;\n render: ComponentType<OpenEditorCustomBlockEditorContext<TData>>;\n};\nexport const defineOpenEditorCustomBlockEditor = <TData extends OpenEditorCustomBlockData>(adapter: OpenEditorCustomBlockEditorAdapter<TData>) => adapter;\n\nexport type OpenEditorCustomBlockEditorSurfaceProps = {\n node: ProseMirrorNode;\n registry: OpenEditorCustomBlockRegistry;\n adapters: readonly OpenEditorCustomBlockEditorAdapter[];\n host: OpenEditorCustomBlockEditorHost;\n updateNode: (node: ProseMirrorNode) => void;\n selected?: boolean;\n fallback?: (input: { node: ProseMirrorNode; status: string }) => ReactNode;\n};\n\nclass EditorAdapterErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {\n state = { failed: false };\n static getDerivedStateFromError() { return { failed: true }; }\n componentDidCatch() {}\n render() {\n return this.state.failed\n ? <div data-openeditor-custom-block-error=\"editor-error\" role=\"status\">Custom block editor unavailable.</div>\n : this.props.children;\n }\n}\n\nconst ReadyEditorSurface = ({ resolved, adapter, host, updateNode, selected }: { resolved: Extract<ReturnType<OpenEditorCustomBlockRegistry[\"resolve\"]>, { status: \"ready\" }>; adapter: OpenEditorCustomBlockEditorAdapter; host: OpenEditorCustomBlockEditorHost; updateNode: (node: ProseMirrorNode) => void; selected: boolean }) => {\n const persistedMigration = useRef<string | null>(null);\n useEffect(() => {\n if (!resolved.migrated) { persistedMigration.current = null; return; }\n const migrationKey = `${String(resolved.node.attrs?.[\"openeditor-id\"])}:${String(resolved.node.attrs?.version)}`;\n if (persistedMigration.current === migrationKey) return;\n persistedMigration.current = migrationKey;\n updateNode(resolved.node);\n }, [resolved.migrated, resolved.node, updateNode]);\n const Render = adapter.render;\n const instanceId = String(resolved.node.attrs?.[\"openeditor-id\"]);\n const updateData = (update: OpenEditorCustomBlockData | ((current: Readonly<OpenEditorCustomBlockData>) => OpenEditorCustomBlockData)) => {\n const data = typeof update === \"function\" ? update(resolved.data) : update;\n updateNode({ ...resolved.node, attrs: { ...resolved.node.attrs, data } });\n };\n return (\n <EditorAdapterErrorBoundary key={`${String(resolved.node.attrs?.[\"openeditor-id\"])}:${resolved.definition.id}`}>\n <Render data={resolved.data} host={host} instanceId={instanceId} selected={selected} updateData={updateData} />\n </EditorAdapterErrorBoundary>\n );\n};\n\nexport const OpenEditorCustomBlockEditorSurface = ({ node, registry, adapters, host, updateNode, selected = false, fallback }: OpenEditorCustomBlockEditorSurfaceProps) => {\n const ids = new Set<string>();\n for (const adapter of adapters) {\n if (ids.has(adapter.block.id)) throw new Error(`Duplicate custom block editor adapter \"${adapter.block.id}\".`);\n ids.add(adapter.block.id);\n }\n const resolved = registry.resolve(node);\n if (resolved.status !== \"ready\") return fallback?.({ node, status: resolved.status }) ?? <div data-openeditor-custom-block-error={resolved.status} role=\"status\">Custom block unavailable: {resolved.status}.</div>;\n const adapter = adapters.find((item) => item.block.id === resolved.definition.id);\n if (!adapter) return fallback?.({ node, status: \"missing-editor\" }) ?? <div data-openeditor-custom-block-error=\"missing-editor\" role=\"status\">Custom block editor unavailable.</div>;\n if (adapter.block !== resolved.definition) return fallback?.({ node, status: \"incompatible-editor\" }) ?? <div data-openeditor-custom-block-error=\"incompatible-editor\" role=\"status\">Custom block editor unavailable.</div>;\n return <ReadyEditorSurface adapter={adapter} host={host} resolved={resolved} selected={selected} updateNode={(next) => { const checked = registry.resolve(next); if (checked.status !== \"ready\") throw new Error(`Invalid data for custom block \"${resolved.definition.id}\".`); updateNode(checked.node); }} />;\n};\n\n/** Installs the editor lifecycle on the one fixed customBlock schema node. */\nexport const createOpenEditorCustomBlockEditorExtension = (options: {\n registry: OpenEditorCustomBlockRegistry;\n adapters: readonly OpenEditorCustomBlockEditorAdapter[];\n host: OpenEditorCustomBlockEditorHost;\n}) => Node.create({\n name: \"customBlock\",\n group: \"block\",\n atom: true,\n isolating: true,\n draggable: true,\n addAttributes: () => ({ \"openeditor-id\": { default: null }, blockId: { default: null }, version: { default: null }, data: { default: {} } }),\n renderHTML: ({ node }) => [\"div\", { \"data-openeditor-custom-block\": node.attrs.blockId ?? \"unknown\" }],\n addNodeView() {\n return ReactNodeViewRenderer((props: ReactNodeViewProps) => (\n <NodeViewWrapper data-openeditor-custom-block={props.node.attrs.blockId ?? \"unknown\"}>\n <OpenEditorCustomBlockEditorSurface\n adapters={options.adapters}\n host={options.host}\n node={props.node.toJSON()}\n registry={options.registry}\n selected={props.selected}\n updateNode={(next) => props.updateAttributes(next.attrs ?? {})}\n />\n </NodeViewWrapper>\n ));\n },\n});\n"]}
1
+ {"version":3,"sources":["../src/editor.tsx"],"names":["adapter"],"mappings":"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","file":"editor.js","sourcesContent":["import type { OpenEditorDocument, ProseMirrorNode } from \"@openeditor/core\";\nimport {\n type OpenEditorCustomBlockData,\n type OpenEditorCustomBlockDefinition,\n type OpenEditorCustomBlockHost,\n type OpenEditorCustomBlockRegistry,\n} from \"./index\";\nimport { Component, useEffect, useRef, type ComponentType, type ReactNode } from \"react\";\nimport { Node } from \"@tiptap/core\";\nimport { NodeViewWrapper, ReactNodeViewRenderer, type ReactNodeViewProps } from \"@tiptap/react\";\n\nexport type OpenEditorCustomBlockDocumentEditorProps = {\n value: OpenEditorDocument;\n onChange: (value: OpenEditorDocument) => void;\n ariaLabel: string;\n};\nexport type OpenEditorCustomBlockEditorHost = OpenEditorCustomBlockHost & {\n fields: { document: ComponentType<OpenEditorCustomBlockDocumentEditorProps> };\n};\nexport type OpenEditorCustomBlockEditorContext<TData extends OpenEditorCustomBlockData> = {\n data: Readonly<TData>;\n instanceId: string;\n host: OpenEditorCustomBlockEditorHost;\n updateData: (update: TData | ((current: Readonly<TData>) => TData)) => void;\n selected: boolean;\n};\nexport type OpenEditorCustomBlockEditorAdapter<TData extends OpenEditorCustomBlockData = any> = {\n block: OpenEditorCustomBlockDefinition<TData>;\n render: ComponentType<OpenEditorCustomBlockEditorContext<TData>>;\n};\nexport const defineOpenEditorCustomBlockEditor = <TData extends OpenEditorCustomBlockData>(adapter: OpenEditorCustomBlockEditorAdapter<TData>) => adapter;\n\ntype OpenEditorCustomBlockEvent = {\n type: string;\n button?: unknown;\n};\n\nexport const shouldStopOpenEditorCustomBlockEvent = (event: OpenEditorCustomBlockEvent): boolean => {\n if (event.type === \"contextmenu\") return true;\n if (event.type !== \"mousedown\" && event.type !== \"pointerdown\") return false;\n return \"button\" in event && typeof event.button === \"number\" && event.button !== 0;\n};\n\nexport const stopOpenEditorCustomBlockEventPropagation = (\n event: OpenEditorCustomBlockEvent & { stopPropagation: () => void },\n): void => {\n if (shouldStopOpenEditorCustomBlockEvent(event)) event.stopPropagation();\n};\n\nexport type OpenEditorCustomBlockEditorSurfaceProps = {\n node: ProseMirrorNode;\n registry: OpenEditorCustomBlockRegistry;\n adapters: readonly OpenEditorCustomBlockEditorAdapter[];\n host: OpenEditorCustomBlockEditorHost;\n updateNode: (node: ProseMirrorNode) => void;\n selected?: boolean;\n fallback?: (input: { node: ProseMirrorNode; status: string }) => ReactNode;\n};\n\nclass EditorAdapterErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {\n state = { failed: false };\n static getDerivedStateFromError() { return { failed: true }; }\n componentDidCatch() {}\n render() {\n return this.state.failed\n ? <div data-openeditor-custom-block-error=\"editor-error\" role=\"status\">Custom block editor unavailable.</div>\n : this.props.children;\n }\n}\n\nconst ReadyEditorSurface = ({ resolved, adapter, host, updateNode, selected }: { resolved: Extract<ReturnType<OpenEditorCustomBlockRegistry[\"resolve\"]>, { status: \"ready\" }>; adapter: OpenEditorCustomBlockEditorAdapter; host: OpenEditorCustomBlockEditorHost; updateNode: (node: ProseMirrorNode) => void; selected: boolean }) => {\n const persistedMigration = useRef<string | null>(null);\n useEffect(() => {\n if (!resolved.migrated) { persistedMigration.current = null; return; }\n const migrationKey = `${String(resolved.node.attrs?.[\"openeditor-id\"])}:${String(resolved.node.attrs?.version)}`;\n if (persistedMigration.current === migrationKey) return;\n persistedMigration.current = migrationKey;\n updateNode(resolved.node);\n }, [resolved.migrated, resolved.node, updateNode]);\n const Render = adapter.render;\n const instanceId = String(resolved.node.attrs?.[\"openeditor-id\"]);\n const updateData = (update: OpenEditorCustomBlockData | ((current: Readonly<OpenEditorCustomBlockData>) => OpenEditorCustomBlockData)) => {\n const data = typeof update === \"function\" ? update(resolved.data) : update;\n updateNode({ ...resolved.node, attrs: { ...resolved.node.attrs, data } });\n };\n return (\n <EditorAdapterErrorBoundary key={`${String(resolved.node.attrs?.[\"openeditor-id\"])}:${resolved.definition.id}`}>\n <Render data={resolved.data} host={host} instanceId={instanceId} selected={selected} updateData={updateData} />\n </EditorAdapterErrorBoundary>\n );\n};\n\nexport const OpenEditorCustomBlockEditorSurface = ({ node, registry, adapters, host, updateNode, selected = false, fallback }: OpenEditorCustomBlockEditorSurfaceProps) => {\n const ids = new Set<string>();\n for (const adapter of adapters) {\n if (ids.has(adapter.block.id)) throw new Error(`Duplicate custom block editor adapter \"${adapter.block.id}\".`);\n ids.add(adapter.block.id);\n }\n const resolved = registry.resolve(node);\n if (resolved.status !== \"ready\") return fallback?.({ node, status: resolved.status }) ?? <div data-openeditor-custom-block-error={resolved.status} role=\"status\">Custom block unavailable: {resolved.status}.</div>;\n const adapter = adapters.find((item) => item.block.id === resolved.definition.id);\n if (!adapter) return fallback?.({ node, status: \"missing-editor\" }) ?? <div data-openeditor-custom-block-error=\"missing-editor\" role=\"status\">Custom block editor unavailable.</div>;\n if (adapter.block !== resolved.definition) return fallback?.({ node, status: \"incompatible-editor\" }) ?? <div data-openeditor-custom-block-error=\"incompatible-editor\" role=\"status\">Custom block editor unavailable.</div>;\n return <ReadyEditorSurface adapter={adapter} host={host} resolved={resolved} selected={selected} updateNode={(next) => { const checked = registry.resolve(next); if (checked.status !== \"ready\") throw new Error(`Invalid data for custom block \"${resolved.definition.id}\".`); updateNode(checked.node); }} />;\n};\n\n/** Installs the editor lifecycle on the one fixed customBlock schema node. */\nexport const createOpenEditorCustomBlockEditorExtension = (options: {\n registry: OpenEditorCustomBlockRegistry;\n adapters: readonly OpenEditorCustomBlockEditorAdapter[];\n host: OpenEditorCustomBlockEditorHost;\n}) => Node.create({\n name: \"customBlock\",\n group: \"block\",\n atom: true,\n isolating: true,\n draggable: true,\n addAttributes: () => ({ \"openeditor-id\": { default: null }, blockId: { default: null }, version: { default: null }, data: { default: {} } }),\n renderHTML: ({ node }) => [\"div\", { \"data-openeditor-custom-block\": node.attrs.blockId ?? \"unknown\" }],\n addNodeView() {\n return ReactNodeViewRenderer((props: ReactNodeViewProps) => (\n <NodeViewWrapper\n data-openeditor-custom-block={props.node.attrs.blockId ?? \"unknown\"}\n onContextMenu={stopOpenEditorCustomBlockEventPropagation}\n onMouseDown={stopOpenEditorCustomBlockEventPropagation}\n onPointerDown={stopOpenEditorCustomBlockEventPropagation}\n >\n <OpenEditorCustomBlockEditorSurface\n adapters={options.adapters}\n host={options.host}\n node={props.node.toJSON()}\n registry={options.registry}\n selected={props.selected}\n updateNode={(next) => props.updateAttributes(next.attrs ?? {})}\n />\n </NodeViewWrapper>\n ));\n },\n});\n"]}
package/dist/index.d.ts CHANGED
@@ -1,17 +1,11 @@
1
- import { JsonValue, OpenEditorDocument, ProseMirrorNode } from '@openeditor/core';
1
+ import { OpenEditorDocument, ProseMirrorNode } from '@openeditor/core';
2
2
  export { JsonObject as OpenEditorCustomBlockJsonObject, JsonValue as OpenEditorCustomBlockJsonValue, OpenEditorDocument } from '@openeditor/core';
3
3
 
4
4
  declare const OPENEDITOR_CUSTOM_BLOCK_NODE: "customBlock";
5
5
 
6
6
  type OpenEditorCustomBlockId = `${string}.${string}`;
7
- /**
8
- * A block-owned data record. The portable schema and bounded JSON validator
9
- * enforce JSON at runtime. `unknown` values at this type boundary also allow
10
- * the structurally wider `OpenEditorDocument` type in declared document fields.
11
- */
12
- type OpenEditorCustomBlockData = Record<string, unknown>;
13
- /** Preserves a block's precise field types, including nested document fields. */
14
- type OpenEditorCustomBlockDataShape<TData extends object> = TData;
7
+ /** A block-owned object. Runtime parsing still requires JSON-only plain data. */
8
+ type OpenEditorCustomBlockData = object;
15
9
  type OpenEditorCustomBlockEnvelope<TData extends OpenEditorCustomBlockData = OpenEditorCustomBlockData> = {
16
10
  blockId: OpenEditorCustomBlockId;
17
11
  version: number;
@@ -25,93 +19,19 @@ type OpenEditorCustomBlockDiagnostic = {
25
19
  path: string;
26
20
  message: string;
27
21
  };
28
- type PortableSchemaBase = {
29
- nullable?: boolean;
30
- enum?: readonly JsonValue[];
22
+ type OpenEditorCustomBlockAssetReference = {
23
+ id: string;
24
+ path: string;
31
25
  };
32
- type OpenEditorCustomBlockDataSchema = PortableSchemaBase & ({
33
- type: "any";
34
- } | {
35
- type: "string";
36
- minLength?: number;
37
- maxLength?: number;
38
- format?: "asset-id";
39
- } | {
40
- type: "number";
41
- integer?: boolean;
42
- minimum?: number;
43
- maximum?: number;
44
- } | {
45
- type: "boolean";
46
- } | {
47
- type: "null";
48
- } | {
49
- type: "oneOf";
50
- variants: readonly OpenEditorCustomBlockDataSchema[];
51
- } | {
52
- type: "document";
53
- } | {
54
- type: "array";
55
- items?: OpenEditorCustomBlockDataSchema;
56
- minItems?: number;
57
- maxItems?: number;
58
- } | {
59
- type: "object";
60
- properties?: Readonly<Record<string, OpenEditorCustomBlockDataSchema>>;
61
- required?: readonly string[];
62
- additionalProperties?: boolean | OpenEditorCustomBlockDataSchema;
63
- });
64
26
  type OpenEditorCustomBlockManifest = {
65
27
  id: OpenEditorCustomBlockId;
66
28
  label: string;
67
29
  version: number;
68
- dataSchema: OpenEditorCustomBlockDataSchema;
69
- constraints?: readonly OpenEditorCustomBlockConstraint[];
70
30
  };
71
- type OpenEditorCustomBlockConstraint = {
72
- kind: "unique";
73
- array: string;
74
- } | {
75
- kind: "uniqueBy";
76
- array: string;
77
- keys: readonly string[];
78
- } | {
79
- kind: "keysIn";
80
- scope?: string;
81
- objects: string;
82
- keys: string;
83
- requireAll?: boolean;
84
- } | {
85
- kind: "reference";
86
- array: string;
87
- field: string;
88
- targetArray: string;
89
- targetField: string;
90
- nullable?: boolean;
91
- } | {
92
- kind: "acyclic";
93
- array: string;
94
- id: string;
95
- parent: string;
96
- } | {
97
- kind: "graph";
98
- array: string;
99
- id: string;
100
- parent: string;
101
- siblingKeys?: readonly string[];
102
- } | {
103
- kind: "url";
104
- path: string;
105
- allowRelative?: boolean;
106
- requireSchemeSeparator?: boolean;
107
- schemes?: readonly string[];
108
- denySchemes?: readonly string[];
109
- when?: {
110
- field: string;
111
- equals: JsonValue;
112
- };
113
- };
114
- type OpenEditorCustomBlockMigration = (data: Readonly<OpenEditorCustomBlockData>) => OpenEditorCustomBlockData;
31
+ type OpenEditorCustomBlockMigration = (input: {
32
+ version: number;
33
+ data: Readonly<OpenEditorCustomBlockData>;
34
+ }) => OpenEditorCustomBlockEnvelope;
115
35
  type OpenEditorCustomBlockStaticContext<TData extends OpenEditorCustomBlockData> = {
116
36
  data: Readonly<TData>;
117
37
  renderDocument: (document: OpenEditorDocument) => OpenEditorCustomBlockSafeHtml;
@@ -122,31 +42,22 @@ type OpenEditorCustomBlockSafeHtml = string | number | null | false | {
122
42
  attrs?: Readonly<Partial<Record<"aria-label" | "aria-current" | "role" | "title" | "href" | "src" | "alt" | "width" | "height" | "start" | "colspan" | "rowspan" | "scope", string | number | undefined>>>;
123
43
  children?: readonly OpenEditorCustomBlockSafeHtml[];
124
44
  };
45
+ /**
46
+ * The complete non-React contract for one installed block. The block package
47
+ * owns parsing because OpenEditor cannot know a third-party data model.
48
+ */
125
49
  type OpenEditorCustomBlockDefinition<TData extends OpenEditorCustomBlockData = any> = {
126
50
  id: OpenEditorCustomBlockId;
127
51
  label: string;
128
52
  version: number;
129
- dataSchema: OpenEditorCustomBlockDataSchema;
130
- initialData: () => TData;
131
- /** Optional in-process refinement. This function is not included in the portable manifest. */
132
- validateData?: (data: Readonly<TData>) => string | readonly string[] | null | undefined;
133
- constraints?: readonly OpenEditorCustomBlockConstraint[];
134
- migrations?: Readonly<Record<number, OpenEditorCustomBlockMigration>>;
53
+ createData: () => TData;
54
+ parseData: (data: unknown) => TData;
55
+ migrate?: OpenEditorCustomBlockMigration;
56
+ assets?: (data: Readonly<TData>) => readonly OpenEditorCustomBlockAssetReference[];
135
57
  toHtml: (context: OpenEditorCustomBlockStaticContext<TData>) => OpenEditorCustomBlockSafeHtml;
136
58
  toText: (context: OpenEditorCustomBlockStaticContext<TData>) => string;
137
59
  manifest: OpenEditorCustomBlockManifest;
138
60
  };
139
- /** Opaque host asset IDs. URLs, paths, whitespace, and control characters are not valid. */
140
- declare const OPENEDITOR_CUSTOM_BLOCK_ASSET_ID_PATTERN: RegExp;
141
- /** Validates one value against a portable JSON-only custom-block schema. */
142
- declare const validateOpenEditorCustomBlockDataValue: (value: unknown, schema: OpenEditorCustomBlockDataSchema) => {
143
- valid: true;
144
- diagnostics: readonly [];
145
- } | {
146
- valid: false;
147
- diagnostics: readonly OpenEditorCustomBlockDiagnostic[];
148
- };
149
- declare const defineOpenEditorCustomBlock: <TData extends OpenEditorCustomBlockData>(input: Omit<OpenEditorCustomBlockDefinition<TData>, "manifest">) => OpenEditorCustomBlockDefinition<TData>;
150
61
  type UnavailableStatus = "missing" | "disabled" | "incompatible" | "invalid";
151
62
  type OpenEditorResolvedCustomBlock<TData extends OpenEditorCustomBlockData = OpenEditorCustomBlockData> = {
152
63
  status: "ready";
@@ -166,50 +77,17 @@ type OpenEditorCustomBlockRegistry = {
166
77
  get: (id: string) => OpenEditorCustomBlockDefinition | undefined;
167
78
  isEnabled: (id: string) => boolean;
168
79
  resolve: (node: ProseMirrorNode) => OpenEditorResolvedCustomBlock;
80
+ validate: (envelope: unknown) => {
81
+ valid: true;
82
+ envelope: OpenEditorCustomBlockEnvelope;
83
+ } | {
84
+ valid: false;
85
+ diagnostics: readonly OpenEditorCustomBlockDiagnostic[];
86
+ };
87
+ assets: (envelope: unknown) => readonly OpenEditorCustomBlockAssetReference[];
169
88
  toHtml: (node: ProseMirrorNode) => string;
170
89
  toText: (node: ProseMirrorNode) => string;
171
90
  };
172
- declare const escapeOpenEditorCustomBlockHtml: (value: unknown) => string;
173
- declare const renderOpenEditorCustomBlockSafeHtml: (value: OpenEditorCustomBlockSafeHtml) => string;
174
- declare const createOpenEditorCustomBlockRegistry: (definitions: readonly OpenEditorCustomBlockDefinition[], options?: {
175
- disabled?: readonly string[];
176
- renderDocument?: (document: OpenEditorDocument) => OpenEditorCustomBlockSafeHtml;
177
- documentToText?: (document: OpenEditorDocument) => string;
178
- }) => OpenEditorCustomBlockRegistry;
179
- declare const createOpenEditorCustomBlockNode: <TData extends OpenEditorCustomBlockData>(registry: OpenEditorCustomBlockRegistry, id: string, data?: TData, options?: {
180
- instanceId?: string;
181
- createInstanceId?: () => string;
182
- }) => OpenEditorCustomBlockNode<TData>;
183
- declare const resolveOpenEditorCustomBlockNode: (registry: OpenEditorCustomBlockRegistry, node: ProseMirrorNode) => OpenEditorResolvedCustomBlock<OpenEditorCustomBlockData>;
184
- declare const validateOpenEditorCustomBlockEnvelope: (value: unknown, manifests: readonly OpenEditorCustomBlockManifest[], options?: {
185
- mode?: "strict" | "preserve";
186
- disabled?: readonly string[];
187
- }) => {
188
- valid: false;
189
- diagnostics: {
190
- path: string;
191
- message: string;
192
- }[];
193
- status?: undefined;
194
- } | {
195
- valid: true;
196
- diagnostics: readonly [];
197
- status: "preserved";
198
- } | {
199
- valid: true;
200
- diagnostics: OpenEditorCustomBlockDiagnostic[];
201
- status: "preserved-invalid";
202
- } | {
203
- valid: true;
204
- diagnostics: readonly [];
205
- status?: undefined;
206
- };
207
- type OpenEditorCustomBlockAssetReference = {
208
- id: string;
209
- path: string;
210
- };
211
- /** Extracts host asset IDs from a portable manifest without loading extension code. */
212
- declare const extractOpenEditorCustomBlockAssetReferences: (value: unknown, manifests: readonly OpenEditorCustomBlockManifest[]) => readonly OpenEditorCustomBlockAssetReference[];
213
91
  type OpenEditorCustomBlockIcon = {
214
92
  id: string;
215
93
  label: string;
@@ -246,12 +124,27 @@ type OpenEditorCustomBlockHost = {
246
124
  } | null>;
247
125
  };
248
126
  };
249
- type OpenEditorCustomBlockDocumentField = {
250
- value: OpenEditorDocument;
251
- onChange?: (value: OpenEditorDocument) => void;
252
- readOnly: boolean;
127
+ declare const defineOpenEditorCustomBlock: <TData extends OpenEditorCustomBlockData>(input: Omit<OpenEditorCustomBlockDefinition<TData>, "manifest">) => OpenEditorCustomBlockDefinition<TData>;
128
+ declare const createOpenEditorCustomBlockRegistry: (definitions: readonly OpenEditorCustomBlockDefinition[], options?: {
129
+ disabled?: readonly string[];
130
+ renderDocument?: (document: OpenEditorDocument) => OpenEditorCustomBlockSafeHtml;
131
+ documentToText?: (document: OpenEditorDocument) => string;
132
+ }) => OpenEditorCustomBlockRegistry;
133
+ declare const createOpenEditorCustomBlockNode: <TData extends OpenEditorCustomBlockData>(registry: OpenEditorCustomBlockRegistry, id: string, data?: TData, options?: {
134
+ instanceId?: string;
135
+ createInstanceId?: () => string;
136
+ }) => OpenEditorCustomBlockNode<TData>;
137
+ declare const resolveOpenEditorCustomBlockNode: (registry: OpenEditorCustomBlockRegistry, node: ProseMirrorNode) => OpenEditorResolvedCustomBlock<object>;
138
+ declare const validateOpenEditorCustomBlockEnvelope: (value: unknown, registry: Pick<OpenEditorCustomBlockRegistry, "validate">) => {
139
+ valid: true;
140
+ envelope: OpenEditorCustomBlockEnvelope;
141
+ } | {
142
+ valid: false;
143
+ diagnostics: readonly OpenEditorCustomBlockDiagnostic[];
253
144
  };
254
- /** Runs portable checks that custom-block packages can include in their test suites. */
145
+ declare const extractOpenEditorCustomBlockAssetReferences: (value: unknown, registry: Pick<OpenEditorCustomBlockRegistry, "assets">) => readonly OpenEditorCustomBlockAssetReference[];
255
146
  declare const conformOpenEditorCustomBlock: (definition: OpenEditorCustomBlockDefinition) => readonly OpenEditorCustomBlockDiagnostic[];
147
+ declare const escapeOpenEditorCustomBlockHtml: (value: unknown) => string;
148
+ declare const renderOpenEditorCustomBlockSafeHtml: (value: OpenEditorCustomBlockSafeHtml) => string;
256
149
 
257
- export { OPENEDITOR_CUSTOM_BLOCK_ASSET_ID_PATTERN, OPENEDITOR_CUSTOM_BLOCK_NODE, type OpenEditorCustomBlockAsset, type OpenEditorCustomBlockAssetReference, type OpenEditorCustomBlockConstraint, type OpenEditorCustomBlockData, type OpenEditorCustomBlockDataSchema, type OpenEditorCustomBlockDataShape, type OpenEditorCustomBlockDefinition, type OpenEditorCustomBlockDiagnostic, type OpenEditorCustomBlockDocumentField, type OpenEditorCustomBlockEnvelope, type OpenEditorCustomBlockHost, type OpenEditorCustomBlockIcon, type OpenEditorCustomBlockId, type OpenEditorCustomBlockManifest, type OpenEditorCustomBlockMigration, type OpenEditorCustomBlockNode, type OpenEditorCustomBlockRegistry, type OpenEditorCustomBlockSafeHtml, type OpenEditorCustomBlockStaticContext, type OpenEditorResolvedCustomBlock, conformOpenEditorCustomBlock, createOpenEditorCustomBlockNode, createOpenEditorCustomBlockRegistry, defineOpenEditorCustomBlock, escapeOpenEditorCustomBlockHtml, extractOpenEditorCustomBlockAssetReferences, renderOpenEditorCustomBlockSafeHtml, resolveOpenEditorCustomBlockNode, validateOpenEditorCustomBlockDataValue, validateOpenEditorCustomBlockEnvelope };
150
+ export { OPENEDITOR_CUSTOM_BLOCK_NODE, type OpenEditorCustomBlockAsset, type OpenEditorCustomBlockAssetReference, type OpenEditorCustomBlockData, type OpenEditorCustomBlockDefinition, type OpenEditorCustomBlockDiagnostic, type OpenEditorCustomBlockEnvelope, type OpenEditorCustomBlockHost, type OpenEditorCustomBlockIcon, type OpenEditorCustomBlockId, type OpenEditorCustomBlockManifest, type OpenEditorCustomBlockMigration, type OpenEditorCustomBlockNode, type OpenEditorCustomBlockRegistry, type OpenEditorCustomBlockSafeHtml, type OpenEditorCustomBlockStaticContext, type OpenEditorResolvedCustomBlock, conformOpenEditorCustomBlock, createOpenEditorCustomBlockNode, createOpenEditorCustomBlockRegistry, defineOpenEditorCustomBlock, escapeOpenEditorCustomBlockHtml, extractOpenEditorCustomBlockAssetReferences, renderOpenEditorCustomBlockSafeHtml, resolveOpenEditorCustomBlockNode, validateOpenEditorCustomBlockEnvelope };