@plenary/react 0.2.0 → 0.2.2
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/dist/bridge.js +3 -1
- package/dist/bridge.js.map +1 -1
- package/dist/components/Text.d.ts +14 -4
- package/dist/components/Text.js +10 -1
- package/dist/components/Text.js.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +4 -12
- package/dist/index.js.map +1 -1
- package/dist/registry.js +22 -48
- package/dist/registry.js.map +1 -1
- package/dist/render.d.ts +6 -8
- package/dist/render.js +40 -49
- package/dist/render.js.map +1 -1
- package/package.json +3 -3
- package/dist/components/Form.d.ts +0 -36
- package/dist/components/Form.js +0 -280
- package/dist/components/Form.js.map +0 -1
- package/dist/context.d.ts +0 -26
- package/dist/context.js +0 -20
- package/dist/context.js.map +0 -1
- package/dist/form.d.ts +0 -25
- package/dist/form.js +0 -11
- package/dist/form.js.map +0 -1
package/dist/render.js
CHANGED
|
@@ -12,19 +12,16 @@ import {
|
|
|
12
12
|
import { registry } from "./registry.js";
|
|
13
13
|
import { Placeholder } from "./components/Placeholder.js";
|
|
14
14
|
import { toResolvedMedia } from "./media.js";
|
|
15
|
-
import { toResolvedForm } from "./form.js";
|
|
16
15
|
const MEDIA_PROP_TYPES = /* @__PURE__ */ new Set(["image", "file"]);
|
|
17
16
|
function mediaPropKeys(reg) {
|
|
18
17
|
return (reg.props ?? []).filter((p) => MEDIA_PROP_TYPES.has(p.type)).map((p) => p.key);
|
|
19
18
|
}
|
|
20
|
-
function formPropKeys(reg) {
|
|
21
|
-
return (reg.props ?? []).filter((p) => p.type === "form").map((p) => p.key);
|
|
22
|
-
}
|
|
23
19
|
function walkNodes(nodes, visit) {
|
|
24
20
|
for (const node of nodes) {
|
|
25
21
|
visit(node);
|
|
26
22
|
if (node.slots) {
|
|
27
|
-
for (const children of Object.values(node.slots))
|
|
23
|
+
for (const children of Object.values(node.slots))
|
|
24
|
+
walkNodes(children, visit);
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
27
|
}
|
|
@@ -52,29 +49,7 @@ async function resolveMediaDetails(document, client) {
|
|
|
52
49
|
);
|
|
53
50
|
return { details, missing };
|
|
54
51
|
}
|
|
55
|
-
|
|
56
|
-
const map = /* @__PURE__ */ new Map();
|
|
57
|
-
if (!client) return map;
|
|
58
|
-
const keys = /* @__PURE__ */ new Set();
|
|
59
|
-
walkNodes(document.root, (node) => {
|
|
60
|
-
const reg = registry.get(node.component)?.registration;
|
|
61
|
-
if (!reg) return;
|
|
62
|
-
for (const key of formPropKeys(reg)) {
|
|
63
|
-
const val = node.props[key];
|
|
64
|
-
if (val && val.form_key && val.definition === void 0) keys.add(val.form_key);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
await Promise.all(
|
|
68
|
-
[...keys].map(async (formKey) => {
|
|
69
|
-
try {
|
|
70
|
-
map.set(formKey, await client.getForm(formKey));
|
|
71
|
-
} catch {
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
return map;
|
|
76
|
-
}
|
|
77
|
-
function effectiveProps(node, reg, mediaDetails, missingMedia, formDefinitions) {
|
|
52
|
+
function effectiveProps(node, reg, mediaDetails, missingMedia) {
|
|
78
53
|
const props = { ...node.props };
|
|
79
54
|
for (const key of Object.values(STYLE_PROP_FOR_BREAKPOINT)) delete props[key];
|
|
80
55
|
for (const key of mediaPropKeys(reg)) {
|
|
@@ -87,12 +62,6 @@ function effectiveProps(node, reg, mediaDetails, missingMedia, formDefinitions)
|
|
|
87
62
|
);
|
|
88
63
|
}
|
|
89
64
|
}
|
|
90
|
-
for (const key of formPropKeys(reg)) {
|
|
91
|
-
const val = props[key];
|
|
92
|
-
if (val) {
|
|
93
|
-
props[key] = toResolvedForm(val, val.form_key ? formDefinitions.get(val.form_key) : void 0);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
65
|
return props;
|
|
97
66
|
}
|
|
98
67
|
function compileStyles(document, cutoffs) {
|
|
@@ -113,21 +82,32 @@ function nodeKey(id, opts) {
|
|
|
113
82
|
}
|
|
114
83
|
function annotate(id, element, opts) {
|
|
115
84
|
if (!opts.annotate && !opts.styledIds.has(id)) return element;
|
|
116
|
-
return /* @__PURE__ */ jsx(
|
|
85
|
+
return /* @__PURE__ */ jsx(
|
|
86
|
+
"div",
|
|
87
|
+
{
|
|
88
|
+
"data-plenary-id": id,
|
|
89
|
+
style: { display: "contents" },
|
|
90
|
+
children: element
|
|
91
|
+
},
|
|
92
|
+
nodeKey(id, opts)
|
|
93
|
+
);
|
|
117
94
|
}
|
|
118
95
|
function renderNode(node, opts) {
|
|
119
96
|
const key = nodeKey(node.id, opts);
|
|
120
97
|
const found = registry.get(node.component);
|
|
121
98
|
if (!found) {
|
|
122
|
-
return annotate(
|
|
99
|
+
return annotate(
|
|
100
|
+
node.id,
|
|
101
|
+
/* @__PURE__ */ jsx(Placeholder, { component: node.component }, key),
|
|
102
|
+
opts
|
|
103
|
+
);
|
|
123
104
|
}
|
|
124
105
|
const { component: Component, registration } = found;
|
|
125
106
|
const props = effectiveProps(
|
|
126
107
|
node,
|
|
127
108
|
registration,
|
|
128
109
|
opts.mediaDetails,
|
|
129
|
-
opts.missingMedia
|
|
130
|
-
opts.formDefinitions
|
|
110
|
+
opts.missingMedia
|
|
131
111
|
);
|
|
132
112
|
if (opts.annotate) props.editor = true;
|
|
133
113
|
let children;
|
|
@@ -140,35 +120,46 @@ function renderNode(node, opts) {
|
|
|
140
120
|
}
|
|
141
121
|
}
|
|
142
122
|
if (slots) props.slots = slots;
|
|
143
|
-
return annotate(
|
|
123
|
+
return annotate(
|
|
124
|
+
node.id,
|
|
125
|
+
createElement(Component, { ...props, key }, children),
|
|
126
|
+
opts
|
|
127
|
+
);
|
|
144
128
|
}
|
|
145
129
|
function renderDocumentSync(document, opts = {}) {
|
|
146
|
-
const { css, styledIds } = compileStyles(
|
|
130
|
+
const { css, styledIds } = compileStyles(
|
|
131
|
+
document,
|
|
132
|
+
resolveCutoffs(registry.getTheme())
|
|
133
|
+
);
|
|
147
134
|
const renderOpts = {
|
|
148
135
|
mediaDetails: opts.mediaDetails ?? /* @__PURE__ */ new Map(),
|
|
149
136
|
missingMedia: opts.missingMedia ?? /* @__PURE__ */ new Set(),
|
|
150
|
-
formDefinitions: opts.formDefinitions ?? /* @__PURE__ */ new Map(),
|
|
151
137
|
annotate: opts.annotate,
|
|
152
138
|
styledIds,
|
|
153
139
|
remountKeys: opts.remountKeys
|
|
154
140
|
};
|
|
155
141
|
return /* @__PURE__ */ jsxs(Suspense, { children: [
|
|
156
|
-
css ? /* @__PURE__ */ jsx(
|
|
142
|
+
css ? /* @__PURE__ */ jsx(
|
|
143
|
+
"style",
|
|
144
|
+
{
|
|
145
|
+
"data-plenary-styles": "",
|
|
146
|
+
dangerouslySetInnerHTML: { __html: css }
|
|
147
|
+
}
|
|
148
|
+
) : null,
|
|
157
149
|
document.root.map((node) => renderNode(node, renderOpts))
|
|
158
150
|
] });
|
|
159
151
|
}
|
|
160
152
|
async function renderDocument(document, ctx = {}) {
|
|
161
|
-
const
|
|
162
|
-
resolveMediaDetails(document, ctx.client),
|
|
163
|
-
resolveFormDefinitions(document, ctx.client)
|
|
164
|
-
]);
|
|
153
|
+
const media = await resolveMediaDetails(document, ctx.client);
|
|
165
154
|
return renderDocumentSync(document, {
|
|
166
155
|
mediaDetails: media.details,
|
|
167
|
-
missingMedia: media.missing
|
|
168
|
-
formDefinitions
|
|
156
|
+
missingMedia: media.missing
|
|
169
157
|
});
|
|
170
158
|
}
|
|
171
|
-
async function PlenaryPage({
|
|
159
|
+
async function PlenaryPage({
|
|
160
|
+
document,
|
|
161
|
+
client
|
|
162
|
+
}) {
|
|
172
163
|
return renderDocument(document, { client });
|
|
173
164
|
}
|
|
174
165
|
export {
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render.tsx"],"sourcesContent":["import { createElement, Fragment, Suspense, type ReactNode } from \"react\";\nimport {\n PlenaryNotFoundError,\n type PlenaryClient,\n type MediaDetail,\n type PublicFormDefinition,\n} from \"@plenary/client\";\nimport {\n STYLE_PROP_FOR_BREAKPOINT,\n compileNodeCss,\n readNodeStyles,\n resolveCutoffs,\n type BreakpointCutoffs,\n type ComponentRegistration,\n type PlenaryDocument,\n type PlenaryNode,\n} from \"@plenary/core\";\nimport { registry } from \"./registry.js\";\nimport { Placeholder } from \"./components/Placeholder.js\";\nimport { toResolvedMedia, type MediaValue } from \"./media.js\";\nimport { toResolvedForm, type FormValue } from \"./form.js\";\nimport type { RenderContext } from \"./types.js\";\n\nconst MEDIA_PROP_TYPES = new Set([\"image\", \"file\"]);\n\n/** Prop keys of a registration whose values are media pointers. */\nfunction mediaPropKeys(reg: ComponentRegistration): string[] {\n return (reg.props ?? []).filter((p) => MEDIA_PROP_TYPES.has(p.type)).map((p) => p.key);\n}\n\n/** Prop keys of a registration whose values are form pointers. */\nfunction formPropKeys(reg: ComponentRegistration): string[] {\n return (reg.props ?? []).filter((p) => p.type === \"form\").map((p) => p.key);\n}\n\nfunction walkNodes(nodes: PlenaryNode[], visit: (n: PlenaryNode) => void): void {\n for (const node of nodes) {\n visit(node);\n if (node.slots) {\n for (const children of Object.values(node.slots)) walkNodes(children, visit);\n }\n }\n}\n\n/**\n * Batch-fetch the detail for every bare `media_id` referenced by a media-typed\n * prop, so nodes can then be rendered synchronously. Values already carrying a\n * URL, and all fetches, are skipped when there is no client.\n *\n * A 404 is reported separately from a failed fetch: it means the asset was\n * deleted while entries still point at it, and such a value must not fall back\n * to the URLs it carries (see `toResolvedMedia`).\n */\nasync function resolveMediaDetails(\n document: PlenaryDocument,\n client: PlenaryClient | undefined,\n): Promise<{ details: Map<string, MediaDetail>; missing: Set<string> }> {\n const details = new Map<string, MediaDetail>();\n const missing = new Set<string>();\n if (!client) return { details, missing };\n\n const ids = new Set<string>();\n walkNodes(document.root, (node) => {\n const reg = registry.get(node.component)?.registration;\n if (!reg) return;\n for (const key of mediaPropKeys(reg)) {\n const val = node.props[key] as MediaValue | undefined;\n if (val && val.media_id && !val.url) ids.add(val.media_id);\n }\n });\n\n await Promise.all(\n [...ids].map(async (id) => {\n try {\n details.set(id, await client.getMedia(id));\n } catch (err) {\n // Anything else degrades to an unresolved value (e.g. a network blip).\n if (err instanceof PlenaryNotFoundError) missing.add(id);\n }\n }),\n );\n return { details, missing };\n}\n\n/**\n * Batch-fetch the render-safe definition for every form referenced by a\n * form-typed prop, so nodes can then be rendered synchronously. Values already\n * carrying a definition (the preview inlines them), and all fetches, are\n * skipped when there is no client.\n */\nasync function resolveFormDefinitions(\n document: PlenaryDocument,\n client: PlenaryClient | undefined,\n): Promise<Map<string, PublicFormDefinition>> {\n const map = new Map<string, PublicFormDefinition>();\n if (!client) return map;\n\n const keys = new Set<string>();\n walkNodes(document.root, (node) => {\n const reg = registry.get(node.component)?.registration;\n if (!reg) return;\n for (const key of formPropKeys(reg)) {\n const val = node.props[key] as FormValue | undefined;\n if (val && val.form_key && val.definition === undefined) keys.add(val.form_key);\n }\n });\n\n await Promise.all(\n [...keys].map(async (formKey) => {\n try {\n map.set(formKey, await client.getForm(formKey));\n } catch {\n // Degrade to an unresolved value (deleted/inactive form, wrong site).\n }\n }),\n );\n return map;\n}\n\n/** Node props with media- and form-typed values replaced by their resolved shape. */\nfunction effectiveProps(\n node: PlenaryNode,\n reg: ComponentRegistration,\n mediaDetails: Map<string, MediaDetail>,\n missingMedia: Set<string>,\n formDefinitions: Map<string, PublicFormDefinition>,\n): Record<string, unknown> {\n const props: Record<string, unknown> = { ...node.props };\n // Editor styles render through the compiled per-node stylesheet, never as\n // props: an inline base style would beat the sheet's media overrides, and\n // components no longer need to do anything for styling to work.\n for (const key of Object.values(STYLE_PROP_FOR_BREAKPOINT)) delete props[key];\n for (const key of mediaPropKeys(reg)) {\n const val = props[key] as MediaValue | undefined;\n if (val) {\n props[key] = toResolvedMedia(\n val,\n val.media_id ? mediaDetails.get(val.media_id) : undefined,\n val.media_id ? missingMedia.has(val.media_id) : false,\n );\n }\n }\n for (const key of formPropKeys(reg)) {\n const val = props[key] as FormValue | undefined;\n if (val) {\n props[key] = toResolvedForm(val, val.form_key ? formDefinitions.get(val.form_key) : undefined);\n }\n }\n return props;\n}\n\n/**\n * Compile every node's style layers into one stylesheet, and remember which\n * nodes contributed — those are the nodes the published render must wrap so\n * the sheet's `[data-plenary-id] > *` selectors have something to target.\n */\nfunction compileStyles(\n document: PlenaryDocument,\n cutoffs: BreakpointCutoffs,\n): { css: string; styledIds: Set<string> } {\n const parts: string[] = [];\n const styledIds = new Set<string>();\n walkNodes(document.root, (node) => {\n const css = compileNodeCss(node.id, readNodeStyles(node.props), cutoffs);\n if (css) {\n parts.push(css);\n styledIds.add(node.id);\n }\n });\n return { css: parts.join(\"\\n\"), styledIds };\n}\n\ninterface RenderOptions {\n mediaDetails: Map<string, MediaDetail>;\n /** Media ids whose asset was deleted — their values render as missing. */\n missingMedia: Set<string>;\n formDefinitions: Map<string, PublicFormDefinition>;\n /** Wrap each node with a `data-plenary-id` marker (preview-bridge mode). */\n annotate?: boolean;\n /** Nodes with compiled styles — wrapped even outside annotate mode. */\n styledIds: Set<string>;\n /**\n * Per-node remount epochs (preview-bridge mode). A bumped epoch changes the\n * node's React key, forcing its subtree to be rebuilt from the document —\n * used after in-place editing, which mutates DOM React never tracked.\n */\n remountKeys?: ReadonlyMap<string, number>;\n}\n\n/** A node's React key: its id, salted by its remount epoch when one exists. */\nfunction nodeKey(id: string, opts: RenderOptions): string {\n const epoch = opts.remountKeys?.get(id);\n return epoch ? `${id}#${epoch}` : id;\n}\n\n/**\n * Wrap a node's element in a layout-neutral marker carrying its id. Every node\n * gets one in annotate mode (the preview bridge maps clicked DOM elements back\n * to nodes through it); on published renders only styled nodes do, since the\n * marker is what the compiled stylesheet's selectors target.\n */\nfunction annotate(id: string, element: ReactNode, opts: RenderOptions): ReactNode {\n if (!opts.annotate && !opts.styledIds.has(id)) return element;\n return (\n <div key={nodeKey(id, opts)} data-plenary-id={id} style={{ display: \"contents\" }}>\n {element}\n </div>\n );\n}\n\nfunction renderNode(node: PlenaryNode, opts: RenderOptions): ReactNode {\n const key = nodeKey(node.id, opts);\n const found = registry.get(node.component);\n if (!found) {\n return annotate(node.id, <Placeholder key={key} component={node.component} />, opts);\n }\n\n const { component: Component, registration } = found;\n const props = effectiveProps(\n node,\n registration,\n opts.mediaDetails,\n opts.missingMedia,\n opts.formDefinitions,\n );\n // Annotated renders are editor previews; the flag lets components show\n // editor-only affordances (e.g. the Image empty-state placeholder) that a\n // published render must not.\n if (opts.annotate) props.editor = true;\n\n // The `children` slot becomes React children; every other slot is delivered\n // via a `slots` prop record of already-rendered nodes.\n let children: ReactNode;\n let slots: Record<string, ReactNode> | undefined;\n if (node.slots) {\n for (const [name, childNodes] of Object.entries(node.slots)) {\n const rendered = (\n <Fragment>{childNodes.map((child) => renderNode(child, opts))}</Fragment>\n );\n if (name === \"children\") children = rendered;\n else (slots ??= {})[name] = rendered;\n }\n }\n if (slots) props.slots = slots;\n\n return annotate(node.id, createElement(Component, { ...props, key }, children), opts);\n}\n\nexport interface SyncRenderOptions {\n /** Pre-fetched media detail, keyed by media_id (empty resolves nothing). */\n mediaDetails?: Map<string, MediaDetail>;\n /** Media ids known to be deleted. The console marks preview values instead. */\n missingMedia?: Set<string>;\n /** Pre-fetched form definitions, keyed by form key (empty resolves nothing). */\n formDefinitions?: Map<string, PublicFormDefinition>;\n /** Wrap nodes with `data-plenary-id` markers (for the preview bridge). */\n annotate?: boolean;\n /** Per-node remount epochs — see RenderOptions.remountKeys. */\n remountKeys?: ReadonlyMap<string, number>;\n}\n\n/**\n * Render a document synchronously from already-resolved inputs. Used by the\n * preview bridge (client-side, no async media pass). Media props that carry a\n * URL still render; bare `media_id`s without a supplied detail stay unresolved.\n *\n * Editor styles ride along as one compiled `<style data-plenary-styles>`\n * element ahead of the tree — real media queries against the registry theme's\n * cutoffs on every render path, preview and published alike (the artboard's\n * locked device widths make them evaluate correctly in the editor). The\n * compiler's sanitizer guarantees the CSS text cannot escape the style element,\n * which is what makes the innerHTML write safe.\n */\nexport function renderDocumentSync(\n document: PlenaryDocument,\n opts: SyncRenderOptions = {},\n): ReactNode {\n const { css, styledIds } = compileStyles(document, resolveCutoffs(registry.getTheme()));\n const renderOpts: RenderOptions = {\n mediaDetails: opts.mediaDetails ?? new Map(),\n missingMedia: opts.missingMedia ?? new Set(),\n formDefinitions: opts.formDefinitions ?? new Map(),\n annotate: opts.annotate,\n styledIds,\n remountKeys: opts.remountKeys,\n };\n return (\n <Suspense>\n {css ? <style data-plenary-styles=\"\" dangerouslySetInnerHTML={{ __html: css }} /> : null}\n {document.root.map((node) => renderNode(node, renderOpts))}\n </Suspense>\n );\n}\n\n/**\n * Render a whole document to a React tree. Resolves media and form definitions\n * first (batched async passes), then walks the tree synchronously. Wrapped in\n * Suspense so lazy (`React.lazy` / `next/dynamic`) custom components are\n * SSR-safe.\n */\nexport async function renderDocument(\n document: PlenaryDocument,\n ctx: RenderContext = {},\n): Promise<ReactNode> {\n const [media, formDefinitions] = await Promise.all([\n resolveMediaDetails(document, ctx.client),\n resolveFormDefinitions(document, ctx.client),\n ]);\n return renderDocumentSync(document, {\n mediaDetails: media.details,\n missingMedia: media.missing,\n formDefinitions,\n });\n}\n\nexport interface PlenaryPageProps {\n document: PlenaryDocument;\n /** Client used to resolve media and form props (optional). */\n client?: PlenaryClient;\n}\n\n/** Renders a Page's `dom` tree. An async server component (SSR-friendly). */\nexport async function PlenaryPage({ document, client }: PlenaryPageProps): Promise<ReactNode> {\n return renderDocument(document, { client });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/render.tsx"],"sourcesContent":["import { createElement, Fragment, Suspense, type ReactNode } from \"react\";\nimport {\n PlenaryNotFoundError,\n type PlenaryClient,\n type MediaDetail,\n} from \"@plenary/client\";\nimport {\n STYLE_PROP_FOR_BREAKPOINT,\n compileNodeCss,\n readNodeStyles,\n resolveCutoffs,\n type BreakpointCutoffs,\n type ComponentRegistration,\n type PlenaryDocument,\n type PlenaryNode,\n} from \"@plenary/core\";\nimport { registry } from \"./registry.js\";\nimport { Placeholder } from \"./components/Placeholder.js\";\nimport { toResolvedMedia, type MediaValue } from \"./media.js\";\nimport type { RenderContext } from \"./types.js\";\n\nconst MEDIA_PROP_TYPES = new Set([\"image\", \"file\"]);\n\n/** Prop keys of a registration whose values are media pointers. */\nfunction mediaPropKeys(reg: ComponentRegistration): string[] {\n return (reg.props ?? [])\n .filter((p) => MEDIA_PROP_TYPES.has(p.type))\n .map((p) => p.key);\n}\n\nfunction walkNodes(\n nodes: PlenaryNode[],\n visit: (n: PlenaryNode) => void,\n): void {\n for (const node of nodes) {\n visit(node);\n if (node.slots) {\n for (const children of Object.values(node.slots))\n walkNodes(children, visit);\n }\n }\n}\n\n/**\n * Batch-fetch the detail for every bare `media_id` referenced by a media-typed\n * prop, so nodes can then be rendered synchronously. Values already carrying a\n * URL, and all fetches, are skipped when there is no client.\n *\n * A 404 is reported separately from a failed fetch: it means the asset was\n * deleted while entries still point at it, and such a value must not fall back\n * to the URLs it carries (see `toResolvedMedia`).\n */\nasync function resolveMediaDetails(\n document: PlenaryDocument,\n client: PlenaryClient | undefined,\n): Promise<{ details: Map<string, MediaDetail>; missing: Set<string> }> {\n const details = new Map<string, MediaDetail>();\n const missing = new Set<string>();\n if (!client) return { details, missing };\n\n const ids = new Set<string>();\n walkNodes(document.root, (node) => {\n const reg = registry.get(node.component)?.registration;\n if (!reg) return;\n for (const key of mediaPropKeys(reg)) {\n const val = node.props[key] as MediaValue | undefined;\n if (val && val.media_id && !val.url) ids.add(val.media_id);\n }\n });\n\n await Promise.all(\n [...ids].map(async (id) => {\n try {\n details.set(id, await client.getMedia(id));\n } catch (err) {\n // Anything else degrades to an unresolved value (e.g. a network blip).\n if (err instanceof PlenaryNotFoundError) missing.add(id);\n }\n }),\n );\n return { details, missing };\n}\n\n/** Node props with media-typed values replaced by their resolved shape. */\nfunction effectiveProps(\n node: PlenaryNode,\n reg: ComponentRegistration,\n mediaDetails: Map<string, MediaDetail>,\n missingMedia: Set<string>,\n): Record<string, unknown> {\n const props: Record<string, unknown> = { ...node.props };\n // Editor styles render through the compiled per-node stylesheet, never as\n // props: an inline base style would beat the sheet's media overrides, and\n // components no longer need to do anything for styling to work.\n for (const key of Object.values(STYLE_PROP_FOR_BREAKPOINT)) delete props[key];\n for (const key of mediaPropKeys(reg)) {\n const val = props[key] as MediaValue | undefined;\n if (val) {\n props[key] = toResolvedMedia(\n val,\n val.media_id ? mediaDetails.get(val.media_id) : undefined,\n val.media_id ? missingMedia.has(val.media_id) : false,\n );\n }\n }\n return props;\n}\n\n/**\n * Compile every node's style layers into one stylesheet, and remember which\n * nodes contributed — those are the nodes the published render must wrap so\n * the sheet's `[data-plenary-id] > *` selectors have something to target.\n */\nfunction compileStyles(\n document: PlenaryDocument,\n cutoffs: BreakpointCutoffs,\n): { css: string; styledIds: Set<string> } {\n const parts: string[] = [];\n const styledIds = new Set<string>();\n walkNodes(document.root, (node) => {\n const css = compileNodeCss(node.id, readNodeStyles(node.props), cutoffs);\n if (css) {\n parts.push(css);\n styledIds.add(node.id);\n }\n });\n return { css: parts.join(\"\\n\"), styledIds };\n}\n\ninterface RenderOptions {\n mediaDetails: Map<string, MediaDetail>;\n /** Media ids whose asset was deleted — their values render as missing. */\n missingMedia: Set<string>;\n /** Wrap each node with a `data-plenary-id` marker (preview-bridge mode). */\n annotate?: boolean;\n /** Nodes with compiled styles — wrapped even outside annotate mode. */\n styledIds: Set<string>;\n /**\n * Per-node remount epochs (preview-bridge mode). A bumped epoch changes the\n * node's React key, forcing its subtree to be rebuilt from the document —\n * used after in-place editing, which mutates DOM React never tracked.\n */\n remountKeys?: ReadonlyMap<string, number>;\n}\n\n/** A node's React key: its id, salted by its remount epoch when one exists. */\nfunction nodeKey(id: string, opts: RenderOptions): string {\n const epoch = opts.remountKeys?.get(id);\n return epoch ? `${id}#${epoch}` : id;\n}\n\n/**\n * Wrap a node's element in a layout-neutral marker carrying its id. Every node\n * gets one in annotate mode (the preview bridge maps clicked DOM elements back\n * to nodes through it); on published renders only styled nodes do, since the\n * marker is what the compiled stylesheet's selectors target.\n */\nfunction annotate(\n id: string,\n element: ReactNode,\n opts: RenderOptions,\n): ReactNode {\n if (!opts.annotate && !opts.styledIds.has(id)) return element;\n return (\n <div\n key={nodeKey(id, opts)}\n data-plenary-id={id}\n style={{ display: \"contents\" }}\n >\n {element}\n </div>\n );\n}\n\nfunction renderNode(node: PlenaryNode, opts: RenderOptions): ReactNode {\n const key = nodeKey(node.id, opts);\n const found = registry.get(node.component);\n if (!found) {\n return annotate(\n node.id,\n <Placeholder key={key} component={node.component} />,\n opts,\n );\n }\n\n const { component: Component, registration } = found;\n const props = effectiveProps(\n node,\n registration,\n opts.mediaDetails,\n opts.missingMedia,\n );\n // Annotated renders are editor previews; the flag lets components show\n // editor-only affordances (e.g. the Image empty-state placeholder) that a\n // published render must not.\n if (opts.annotate) props.editor = true;\n\n // The `children` slot becomes React children; every other slot is delivered\n // via a `slots` prop record of already-rendered nodes.\n let children: ReactNode;\n let slots: Record<string, ReactNode> | undefined;\n if (node.slots) {\n for (const [name, childNodes] of Object.entries(node.slots)) {\n const rendered = (\n <Fragment>\n {childNodes.map((child) => renderNode(child, opts))}\n </Fragment>\n );\n if (name === \"children\") children = rendered;\n else (slots ??= {})[name] = rendered;\n }\n }\n if (slots) props.slots = slots;\n\n return annotate(\n node.id,\n createElement(Component, { ...props, key }, children),\n opts,\n );\n}\n\nexport interface SyncRenderOptions {\n /** Pre-fetched media detail, keyed by media_id (empty resolves nothing). */\n mediaDetails?: Map<string, MediaDetail>;\n /** Media ids known to be deleted. The console marks preview values instead. */\n missingMedia?: Set<string>;\n /** Pre-fetched form definitions, keyed by form key (empty resolves nothing). */\n /** Wrap nodes with `data-plenary-id` markers (for the preview bridge). */\n annotate?: boolean;\n /** Per-node remount epochs — see RenderOptions.remountKeys. */\n remountKeys?: ReadonlyMap<string, number>;\n}\n\n/**\n * Render a document synchronously from already-resolved inputs. Used by the\n * preview bridge (client-side, no async media pass). Media props that carry a\n * URL still render; bare `media_id`s without a supplied detail stay unresolved.\n *\n * Editor styles ride along as one compiled `<style data-plenary-styles>`\n * element ahead of the tree — real media queries against the registry theme's\n * cutoffs on every render path, preview and published alike (the artboard's\n * locked device widths make them evaluate correctly in the editor). The\n * compiler's sanitizer guarantees the CSS text cannot escape the style element,\n * which is what makes the innerHTML write safe.\n */\nexport function renderDocumentSync(\n document: PlenaryDocument,\n opts: SyncRenderOptions = {},\n): ReactNode {\n const { css, styledIds } = compileStyles(\n document,\n resolveCutoffs(registry.getTheme()),\n );\n const renderOpts: RenderOptions = {\n mediaDetails: opts.mediaDetails ?? new Map(),\n missingMedia: opts.missingMedia ?? new Set(),\n annotate: opts.annotate,\n styledIds,\n remountKeys: opts.remountKeys,\n };\n return (\n <Suspense>\n {css ? (\n <style\n data-plenary-styles=\"\"\n dangerouslySetInnerHTML={{ __html: css }}\n />\n ) : null}\n {document.root.map((node) => renderNode(node, renderOpts))}\n </Suspense>\n );\n}\n\n/**\n * Render a whole document to a React tree. Resolves media first (one batched\n * async pass), then walks the tree synchronously. Wrapped in Suspense so lazy\n * (`React.lazy` / `next/dynamic`) custom components are SSR-safe.\n */\nexport async function renderDocument(\n document: PlenaryDocument,\n ctx: RenderContext = {},\n): Promise<ReactNode> {\n const media = await resolveMediaDetails(document, ctx.client);\n return renderDocumentSync(document, {\n mediaDetails: media.details,\n missingMedia: media.missing,\n });\n}\n\nexport interface PlenaryPageProps {\n document: PlenaryDocument;\n /** Client used to resolve media props (optional). */\n client?: PlenaryClient;\n}\n\n/** Renders a Page's `dom` tree. An async server component (SSR-friendly). */\nexport async function PlenaryPage({\n document,\n client,\n}: PlenaryPageProps): Promise<ReactNode> {\n return renderDocument(document, { client });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plenary/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Plenary React renderer, component registry, built-in components, and preview bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@plenary/client": "0.2.
|
|
24
|
-
"@plenary/core": "0.2.
|
|
23
|
+
"@plenary/client": "0.2.2",
|
|
24
|
+
"@plenary/core": "0.2.2"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "^18 || ^19",
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { CSSProperties } from 'react';
|
|
3
|
-
import { ResolvedForm, FormValue } from '../form.js';
|
|
4
|
-
import '@plenary/client';
|
|
5
|
-
|
|
6
|
-
interface FormProps {
|
|
7
|
-
/** A resolved form value (the renderer resolves it before render). */
|
|
8
|
-
form?: ResolvedForm | FormValue | null;
|
|
9
|
-
showTitle?: boolean;
|
|
10
|
-
submitLabel?: string;
|
|
11
|
-
onSuccess?: "message" | "redirect";
|
|
12
|
-
successMessage?: string;
|
|
13
|
-
redirectUrl?: string;
|
|
14
|
-
className?: string;
|
|
15
|
-
style?: CSSProperties;
|
|
16
|
-
/** True on editor-preview renders; enables the no-form placeholder. */
|
|
17
|
-
editor?: boolean;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Renders a Form Manager form from its resolved definition and submits entries
|
|
21
|
-
* through the provided client (see `PlenaryProvider`). On success it either
|
|
22
|
-
* swaps itself for `successMessage` or navigates to `redirectUrl`.
|
|
23
|
-
*
|
|
24
|
-
* Editor previews render the real fields inert (a disabled fieldset), so the
|
|
25
|
-
* block is selectable on the canvas without being fillable; the bridge also
|
|
26
|
-
* cancels any submit while the console drives the iframe. Without a chosen
|
|
27
|
-
* form it renders a placeholder in the editor and nothing on a published page.
|
|
28
|
-
*
|
|
29
|
-
* The hidden `website` input is the server's reserved honeypot key and the
|
|
30
|
-
* `ts` token (when the definition carries one) is echoed back — both are
|
|
31
|
-
* ignored by servers without anti-spam enabled. The honeypot is skipped in the
|
|
32
|
-
* rare case the form itself defines a `website` field.
|
|
33
|
-
*/
|
|
34
|
-
declare function Form({ form, showTitle, submitLabel, onSuccess, successMessage, redirectUrl, className, style, editor, }: FormProps): react.JSX.Element | null;
|
|
35
|
-
|
|
36
|
-
export { Form, type FormProps };
|
package/dist/components/Form.js
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId, useRef, useState } from "react";
|
|
4
|
-
import { PlenaryError } from "@plenary/client";
|
|
5
|
-
import { usePlenaryClient } from "../context.js";
|
|
6
|
-
const UNSUPPORTED_TYPES = /* @__PURE__ */ new Set(["object", "list", "json"]);
|
|
7
|
-
const FIELD_ERROR_MESSAGES = {
|
|
8
|
-
entry_field_required: "This field is required.",
|
|
9
|
-
entry_field_required_nonempty: "This field is required.",
|
|
10
|
-
entry_field_value_required: "This field is required.",
|
|
11
|
-
entry_field_value_taken: "This value is already taken."
|
|
12
|
-
};
|
|
13
|
-
function FormPlaceholder({ className, style }) {
|
|
14
|
-
return /* @__PURE__ */ jsxs(
|
|
15
|
-
"div",
|
|
16
|
-
{
|
|
17
|
-
"data-plenary-form-placeholder": "",
|
|
18
|
-
className,
|
|
19
|
-
style: {
|
|
20
|
-
display: "flex",
|
|
21
|
-
alignItems: "center",
|
|
22
|
-
justifyContent: "center",
|
|
23
|
-
gap: 8,
|
|
24
|
-
minHeight: 120,
|
|
25
|
-
border: "1px dashed #94a3b8",
|
|
26
|
-
borderRadius: 4,
|
|
27
|
-
background: "#f1f5f9",
|
|
28
|
-
color: "#94a3b8",
|
|
29
|
-
...style
|
|
30
|
-
},
|
|
31
|
-
children: [
|
|
32
|
-
/* @__PURE__ */ jsxs(
|
|
33
|
-
"svg",
|
|
34
|
-
{
|
|
35
|
-
width: "32",
|
|
36
|
-
height: "32",
|
|
37
|
-
viewBox: "0 0 24 24",
|
|
38
|
-
fill: "none",
|
|
39
|
-
stroke: "currentColor",
|
|
40
|
-
strokeWidth: "1.5",
|
|
41
|
-
strokeLinecap: "round",
|
|
42
|
-
strokeLinejoin: "round",
|
|
43
|
-
"aria-hidden": "true",
|
|
44
|
-
children: [
|
|
45
|
-
/* @__PURE__ */ jsx("rect", { x: "4", y: "3", width: "16", height: "18", rx: "2" }),
|
|
46
|
-
/* @__PURE__ */ jsx("path", { d: "M8 8h8M8 12h8M8 16h4" })
|
|
47
|
-
]
|
|
48
|
-
}
|
|
49
|
-
),
|
|
50
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: "Choose a form in the Properties tab" })
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
function collectValues(fields, formEl) {
|
|
56
|
-
const fd = new FormData(formEl);
|
|
57
|
-
const data = {};
|
|
58
|
-
for (const f of fields) {
|
|
59
|
-
if (UNSUPPORTED_TYPES.has(f.type)) continue;
|
|
60
|
-
switch (f.type) {
|
|
61
|
-
case "boolean":
|
|
62
|
-
data[f.key] = fd.get(f.key) === "on";
|
|
63
|
-
break;
|
|
64
|
-
case "multi_select": {
|
|
65
|
-
const values = fd.getAll(f.key).map(String);
|
|
66
|
-
if (values.length) data[f.key] = values;
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
case "tags": {
|
|
70
|
-
const values = String(fd.get(f.key) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
71
|
-
if (values.length) data[f.key] = values;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
case "number": {
|
|
75
|
-
const raw = String(fd.get(f.key) ?? "").trim();
|
|
76
|
-
if (raw !== "") data[f.key] = Number(raw);
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
default: {
|
|
80
|
-
const raw = String(fd.get(f.key) ?? "");
|
|
81
|
-
if (raw !== "") data[f.key] = raw;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return data;
|
|
86
|
-
}
|
|
87
|
-
function FieldInput({ field, id }) {
|
|
88
|
-
const ui = field.ui ?? {};
|
|
89
|
-
const validation = field.validation ?? {};
|
|
90
|
-
const common = {
|
|
91
|
-
id,
|
|
92
|
-
name: field.key,
|
|
93
|
-
required: field.required,
|
|
94
|
-
className: "plenary-form__input",
|
|
95
|
-
placeholder: ui.placeholder || void 0
|
|
96
|
-
};
|
|
97
|
-
switch (field.type) {
|
|
98
|
-
case "text":
|
|
99
|
-
if (ui.control === "textarea") {
|
|
100
|
-
return /* @__PURE__ */ jsx("textarea", { ...common, maxLength: validation.max_length });
|
|
101
|
-
}
|
|
102
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "text", maxLength: validation.max_length });
|
|
103
|
-
case "email":
|
|
104
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "email" });
|
|
105
|
-
case "url":
|
|
106
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "url" });
|
|
107
|
-
case "phone":
|
|
108
|
-
return /* @__PURE__ */ jsx(
|
|
109
|
-
"input",
|
|
110
|
-
{
|
|
111
|
-
...common,
|
|
112
|
-
type: "tel",
|
|
113
|
-
pattern: "\\+[1-9][0-9]{1,14}",
|
|
114
|
-
title: "International format, e.g. +15551234567",
|
|
115
|
-
placeholder: ui.placeholder || "+15551234567"
|
|
116
|
-
}
|
|
117
|
-
);
|
|
118
|
-
case "number":
|
|
119
|
-
return /* @__PURE__ */ jsx(
|
|
120
|
-
"input",
|
|
121
|
-
{
|
|
122
|
-
...common,
|
|
123
|
-
type: "number",
|
|
124
|
-
min: validation.min_val,
|
|
125
|
-
max: validation.max_val,
|
|
126
|
-
step: validation.multiple_of ?? "any"
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
case "date":
|
|
130
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "date" });
|
|
131
|
-
case "time":
|
|
132
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "time" });
|
|
133
|
-
case "datetime":
|
|
134
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "datetime-local" });
|
|
135
|
-
case "boolean":
|
|
136
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "checkbox", className: "plenary-form__checkbox" });
|
|
137
|
-
case "select": {
|
|
138
|
-
const options = (field.settings?.options ?? []).filter(
|
|
139
|
-
(o) => o && o.value !== void 0
|
|
140
|
-
);
|
|
141
|
-
if (ui.control === "radio" || ui.control === "segmented") {
|
|
142
|
-
return /* @__PURE__ */ jsx("div", { className: "plenary-form__options", role: "radiogroup", children: options.map((o, i) => /* @__PURE__ */ jsxs("label", { className: "plenary-form__option", children: [
|
|
143
|
-
/* @__PURE__ */ jsx(
|
|
144
|
-
"input",
|
|
145
|
-
{
|
|
146
|
-
type: "radio",
|
|
147
|
-
name: field.key,
|
|
148
|
-
value: o.value,
|
|
149
|
-
required: field.required && i === 0
|
|
150
|
-
}
|
|
151
|
-
),
|
|
152
|
-
" ",
|
|
153
|
-
o.label
|
|
154
|
-
] }, o.value)) });
|
|
155
|
-
}
|
|
156
|
-
return /* @__PURE__ */ jsxs("select", { ...common, children: [
|
|
157
|
-
!field.required && /* @__PURE__ */ jsx("option", { value: "" }),
|
|
158
|
-
options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
|
|
159
|
-
] });
|
|
160
|
-
}
|
|
161
|
-
case "multi_select": {
|
|
162
|
-
const options = field.settings?.options ?? [];
|
|
163
|
-
return /* @__PURE__ */ jsx("div", { className: "plenary-form__options", children: options.map((o) => /* @__PURE__ */ jsxs("label", { className: "plenary-form__option", children: [
|
|
164
|
-
/* @__PURE__ */ jsx("input", { type: "checkbox", name: field.key, value: o.value }),
|
|
165
|
-
" ",
|
|
166
|
-
o.label
|
|
167
|
-
] }, o.value)) });
|
|
168
|
-
}
|
|
169
|
-
case "tags":
|
|
170
|
-
return /* @__PURE__ */ jsx("input", { ...common, type: "text", placeholder: ui.placeholder || "One, two, three" });
|
|
171
|
-
default:
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
function Form({
|
|
176
|
-
form,
|
|
177
|
-
showTitle,
|
|
178
|
-
submitLabel,
|
|
179
|
-
onSuccess,
|
|
180
|
-
successMessage,
|
|
181
|
-
redirectUrl,
|
|
182
|
-
className,
|
|
183
|
-
style,
|
|
184
|
-
editor
|
|
185
|
-
}) {
|
|
186
|
-
const client = usePlenaryClient();
|
|
187
|
-
const uid = useId();
|
|
188
|
-
const honeypotRef = useRef(null);
|
|
189
|
-
const [submitting, setSubmitting] = useState(false);
|
|
190
|
-
const [submitted, setSubmitted] = useState(false);
|
|
191
|
-
const [error, setError] = useState(null);
|
|
192
|
-
const definition = form?.definition;
|
|
193
|
-
if (!form?.form_key || !definition) {
|
|
194
|
-
return editor ? /* @__PURE__ */ jsx(FormPlaceholder, { className, style }) : null;
|
|
195
|
-
}
|
|
196
|
-
const fields = definition.fields ?? [];
|
|
197
|
-
const honeypot = !fields.some((f) => f.key === "website");
|
|
198
|
-
if (submitted) {
|
|
199
|
-
return /* @__PURE__ */ jsx("div", { className: className ? `plenary-form__success ${className}` : "plenary-form__success", style, role: "status", children: successMessage || "Thanks! Your submission has been received." });
|
|
200
|
-
}
|
|
201
|
-
async function handleSubmit(e) {
|
|
202
|
-
e.preventDefault();
|
|
203
|
-
if (editor || submitting) return;
|
|
204
|
-
const formEl = e.currentTarget;
|
|
205
|
-
if (!client) {
|
|
206
|
-
setError({ message: "This form is not connected \u2014 the page is missing a PlenaryProvider." });
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
setError(null);
|
|
210
|
-
setSubmitting(true);
|
|
211
|
-
try {
|
|
212
|
-
await client.submitForm(form.form_key, collectValues(fields, formEl), {
|
|
213
|
-
...honeypot && honeypotRef.current ? { website: honeypotRef.current.value } : {},
|
|
214
|
-
...definition.ts ? { ts: definition.ts } : {}
|
|
215
|
-
});
|
|
216
|
-
if (onSuccess === "redirect" && redirectUrl) {
|
|
217
|
-
window.location.assign(redirectUrl);
|
|
218
|
-
} else {
|
|
219
|
-
setSubmitted(true);
|
|
220
|
-
}
|
|
221
|
-
} catch (err) {
|
|
222
|
-
if (err instanceof PlenaryError && typeof err.vars?.field === "string") {
|
|
223
|
-
setError({
|
|
224
|
-
field: err.vars.field,
|
|
225
|
-
message: FIELD_ERROR_MESSAGES[err.code] ?? "This value is invalid."
|
|
226
|
-
});
|
|
227
|
-
} else if (err instanceof PlenaryError && err.status === 429) {
|
|
228
|
-
setError({ message: "Too many submissions \u2014 please try again in a minute." });
|
|
229
|
-
} else {
|
|
230
|
-
setError({ message: "Something went wrong \u2014 please try again." });
|
|
231
|
-
}
|
|
232
|
-
} finally {
|
|
233
|
-
setSubmitting(false);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return /* @__PURE__ */ jsxs("form", { className: className ? `plenary-form ${className}` : "plenary-form", style, onSubmit: handleSubmit, noValidate: false, children: [
|
|
237
|
-
showTitle && /* @__PURE__ */ jsx("h2", { className: "plenary-form__title", children: definition.name }),
|
|
238
|
-
/* @__PURE__ */ jsxs(
|
|
239
|
-
"fieldset",
|
|
240
|
-
{
|
|
241
|
-
disabled: editor || submitting,
|
|
242
|
-
style: { border: 0, padding: 0, margin: 0, minInlineSize: 0 },
|
|
243
|
-
children: [
|
|
244
|
-
fields.map((f) => {
|
|
245
|
-
const id = `${uid}-${f.key}`;
|
|
246
|
-
if (UNSUPPORTED_TYPES.has(f.type)) {
|
|
247
|
-
return /* @__PURE__ */ jsx("div", { className: "plenary-form__field plenary-form__field--unsupported", children: /* @__PURE__ */ jsxs("p", { className: "plenary-form__notice", children: [
|
|
248
|
-
"\u201C",
|
|
249
|
-
f.name,
|
|
250
|
-
"\u201D uses a field type the Form block does not support."
|
|
251
|
-
] }) }, f.key);
|
|
252
|
-
}
|
|
253
|
-
const isCheckbox = f.type === "boolean";
|
|
254
|
-
const label = /* @__PURE__ */ jsxs("label", { className: "plenary-form__label", htmlFor: isCheckbox ? void 0 : id, children: [
|
|
255
|
-
isCheckbox && /* @__PURE__ */ jsx(FieldInput, { field: f, id }),
|
|
256
|
-
f.name,
|
|
257
|
-
f.required ? " *" : ""
|
|
258
|
-
] });
|
|
259
|
-
return /* @__PURE__ */ jsxs("div", { className: "plenary-form__field", children: [
|
|
260
|
-
label,
|
|
261
|
-
!isCheckbox && /* @__PURE__ */ jsx(FieldInput, { field: f, id }),
|
|
262
|
-
typeof f.ui?.help_text === "string" && f.ui.help_text !== "" && /* @__PURE__ */ jsx("p", { className: "plenary-form__help", children: f.ui.help_text }),
|
|
263
|
-
error?.field === f.key && /* @__PURE__ */ jsx("p", { className: "plenary-form__error", role: "alert", children: error.message })
|
|
264
|
-
] }, f.key);
|
|
265
|
-
}),
|
|
266
|
-
honeypot && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", left: "-9999px", width: 1, height: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsxs("label", { children: [
|
|
267
|
-
"Leave this field blank",
|
|
268
|
-
/* @__PURE__ */ jsx("input", { ref: honeypotRef, type: "text", name: "website", tabIndex: -1, autoComplete: "off" })
|
|
269
|
-
] }) }),
|
|
270
|
-
error && !error.field && /* @__PURE__ */ jsx("p", { className: "plenary-form__error", role: "alert", children: error.message }),
|
|
271
|
-
/* @__PURE__ */ jsx("div", { className: "plenary-form__actions", children: /* @__PURE__ */ jsx("button", { type: "submit", className: "plenary-form__submit", children: submitLabel || "Submit" }) })
|
|
272
|
-
]
|
|
273
|
-
}
|
|
274
|
-
)
|
|
275
|
-
] });
|
|
276
|
-
}
|
|
277
|
-
export {
|
|
278
|
-
Form
|
|
279
|
-
};
|
|
280
|
-
//# sourceMappingURL=Form.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Form.tsx"],"sourcesContent":["\"use client\";\n\nimport { useId, useRef, useState, type CSSProperties, type FormEvent, type ReactNode } from \"react\";\nimport { PlenaryError, type PublicFormField } from \"@plenary/client\";\nimport type { FormValue, ResolvedForm } from \"../form.js\";\nimport { usePlenaryClient } from \"../context.js\";\n\nexport interface FormProps {\n /** A resolved form value (the renderer resolves it before render). */\n form?: ResolvedForm | FormValue | null;\n showTitle?: boolean;\n submitLabel?: string;\n onSuccess?: \"message\" | \"redirect\";\n successMessage?: string;\n redirectUrl?: string;\n className?: string;\n style?: CSSProperties;\n /** True on editor-preview renders; enables the no-form placeholder. */\n editor?: boolean;\n}\n\n/** Field types the block renders no input for (v1) — a form needing them wants a custom implementation. */\nconst UNSUPPORTED_TYPES = new Set([\"object\", \"list\", \"json\"]);\n\n/** English messages for the per-field validation codes the submit endpoint returns. */\nconst FIELD_ERROR_MESSAGES: Record<string, string> = {\n entry_field_required: \"This field is required.\",\n entry_field_required_nonempty: \"This field is required.\",\n entry_field_value_required: \"This field is required.\",\n entry_field_value_taken: \"This value is already taken.\",\n};\n\n/** An empty-state stand-in so a form-less Form block is visible and selectable. */\nfunction FormPlaceholder({ className, style }: { className?: string; style?: CSSProperties }) {\n return (\n <div\n data-plenary-form-placeholder=\"\"\n className={className}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 8,\n minHeight: 120,\n border: \"1px dashed #94a3b8\",\n borderRadius: 4,\n background: \"#f1f5f9\",\n color: \"#94a3b8\",\n ...style,\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"2\" />\n <path d=\"M8 8h8M8 12h8M8 16h4\" />\n </svg>\n <span style={{ fontSize: 13 }}>Choose a form in the Properties tab</span>\n </div>\n );\n}\n\n/** Read the submitted values off the native form, typed per field definition. */\nfunction collectValues(fields: PublicFormField[], formEl: HTMLFormElement): Record<string, unknown> {\n const fd = new FormData(formEl);\n const data: Record<string, unknown> = {};\n for (const f of fields) {\n if (UNSUPPORTED_TYPES.has(f.type)) continue;\n switch (f.type) {\n case \"boolean\":\n data[f.key] = fd.get(f.key) === \"on\";\n break;\n case \"multi_select\": {\n const values = fd.getAll(f.key).map(String);\n if (values.length) data[f.key] = values;\n break;\n }\n case \"tags\": {\n const values = String(fd.get(f.key) ?? \"\")\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (values.length) data[f.key] = values;\n break;\n }\n case \"number\": {\n const raw = String(fd.get(f.key) ?? \"\").trim();\n if (raw !== \"\") data[f.key] = Number(raw);\n break;\n }\n default: {\n // Empty optional inputs are omitted rather than sent as \"\" — the\n // server format-validates present values (e.g. phone's E.164 rule).\n const raw = String(fd.get(f.key) ?? \"\");\n if (raw !== \"\") data[f.key] = raw;\n }\n }\n }\n return data;\n}\n\ninterface FieldInputProps {\n field: PublicFormField;\n id: string;\n}\n\n/** The input element(s) for one field, driven by its type and `ui`/`settings`. */\nfunction FieldInput({ field, id }: FieldInputProps) {\n const ui = field.ui ?? {};\n const validation = field.validation ?? {};\n const common = {\n id,\n name: field.key,\n required: field.required,\n className: \"plenary-form__input\",\n placeholder: (ui.placeholder as string) || undefined,\n };\n\n switch (field.type) {\n case \"text\":\n if (ui.control === \"textarea\") {\n return <textarea {...common} maxLength={validation.max_length as number | undefined} />;\n }\n return <input {...common} type=\"text\" maxLength={validation.max_length as number | undefined} />;\n case \"email\":\n return <input {...common} type=\"email\" />;\n case \"url\":\n return <input {...common} type=\"url\" />;\n case \"phone\":\n // The server enforces E.164; the pattern lets the browser say so first.\n return (\n <input\n {...common}\n type=\"tel\"\n pattern=\"\\+[1-9][0-9]{1,14}\"\n title=\"International format, e.g. +15551234567\"\n placeholder={(ui.placeholder as string) || \"+15551234567\"}\n />\n );\n case \"number\":\n return (\n <input\n {...common}\n type=\"number\"\n min={validation.min_val as number | undefined}\n max={validation.max_val as number | undefined}\n step={(validation.multiple_of as number | undefined) ?? \"any\"}\n />\n );\n case \"date\":\n return <input {...common} type=\"date\" />;\n case \"time\":\n return <input {...common} type=\"time\" />;\n case \"datetime\":\n return <input {...common} type=\"datetime-local\" />;\n case \"boolean\":\n return <input {...common} type=\"checkbox\" className=\"plenary-form__checkbox\" />;\n case \"select\": {\n const options = ((field.settings?.options as { value: string; label: string }[]) ?? []).filter(\n (o) => o && o.value !== undefined,\n );\n if (ui.control === \"radio\" || ui.control === \"segmented\") {\n return (\n <div className=\"plenary-form__options\" role=\"radiogroup\">\n {options.map((o, i) => (\n <label key={o.value} className=\"plenary-form__option\">\n <input\n type=\"radio\"\n name={field.key}\n value={o.value}\n required={field.required && i === 0}\n />{\" \"}\n {o.label}\n </label>\n ))}\n </div>\n );\n }\n return (\n <select {...common}>\n {!field.required && <option value=\"\"></option>}\n {options.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n );\n }\n case \"multi_select\": {\n const options = (field.settings?.options as { value: string; label: string }[]) ?? [];\n return (\n <div className=\"plenary-form__options\">\n {options.map((o) => (\n <label key={o.value} className=\"plenary-form__option\">\n <input type=\"checkbox\" name={field.key} value={o.value} /> {o.label}\n </label>\n ))}\n </div>\n );\n }\n case \"tags\":\n return <input {...common} type=\"text\" placeholder={(ui.placeholder as string) || \"One, two, three\"} />;\n default:\n return null;\n }\n}\n\n/**\n * Renders a Form Manager form from its resolved definition and submits entries\n * through the provided client (see `PlenaryProvider`). On success it either\n * swaps itself for `successMessage` or navigates to `redirectUrl`.\n *\n * Editor previews render the real fields inert (a disabled fieldset), so the\n * block is selectable on the canvas without being fillable; the bridge also\n * cancels any submit while the console drives the iframe. Without a chosen\n * form it renders a placeholder in the editor and nothing on a published page.\n *\n * The hidden `website` input is the server's reserved honeypot key and the\n * `ts` token (when the definition carries one) is echoed back — both are\n * ignored by servers without anti-spam enabled. The honeypot is skipped in the\n * rare case the form itself defines a `website` field.\n */\nexport function Form({\n form,\n showTitle,\n submitLabel,\n onSuccess,\n successMessage,\n redirectUrl,\n className,\n style,\n editor,\n}: FormProps) {\n const client = usePlenaryClient();\n const uid = useId();\n const honeypotRef = useRef<HTMLInputElement>(null);\n const [submitting, setSubmitting] = useState(false);\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<{ field?: string; message: string } | null>(null);\n\n const definition = form?.definition;\n if (!form?.form_key || !definition) {\n return editor ? <FormPlaceholder className={className} style={style} /> : null;\n }\n\n const fields = definition.fields ?? [];\n const honeypot = !fields.some((f) => f.key === \"website\");\n\n if (submitted) {\n return (\n <div className={className ? `plenary-form__success ${className}` : \"plenary-form__success\"} style={style} role=\"status\">\n {successMessage || \"Thanks! Your submission has been received.\"}\n </div>\n );\n }\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>) {\n e.preventDefault();\n if (editor || submitting) return;\n const formEl = e.currentTarget;\n if (!client) {\n setError({ message: \"This form is not connected — the page is missing a PlenaryProvider.\" });\n return;\n }\n setError(null);\n setSubmitting(true);\n try {\n await client.submitForm(form!.form_key!, collectValues(fields, formEl), {\n ...(honeypot && honeypotRef.current ? { website: honeypotRef.current.value } : {}),\n ...(definition!.ts ? { ts: definition!.ts } : {}),\n });\n if (onSuccess === \"redirect\" && redirectUrl) {\n window.location.assign(redirectUrl);\n } else {\n setSubmitted(true);\n }\n } catch (err) {\n if (err instanceof PlenaryError && typeof err.vars?.field === \"string\") {\n setError({\n field: err.vars.field,\n message: FIELD_ERROR_MESSAGES[err.code] ?? \"This value is invalid.\",\n });\n } else if (err instanceof PlenaryError && err.status === 429) {\n setError({ message: \"Too many submissions — please try again in a minute.\" });\n } else {\n setError({ message: \"Something went wrong — please try again.\" });\n }\n } finally {\n setSubmitting(false);\n }\n }\n\n return (\n <form className={className ? `plenary-form ${className}` : \"plenary-form\"} style={style} onSubmit={handleSubmit} noValidate={false}>\n {showTitle && <h2 className=\"plenary-form__title\">{definition.name}</h2>}\n <fieldset\n disabled={editor || submitting}\n style={{ border: 0, padding: 0, margin: 0, minInlineSize: 0 }}\n >\n {fields.map((f) => {\n const id = `${uid}-${f.key}`;\n if (UNSUPPORTED_TYPES.has(f.type)) {\n return (\n <div key={f.key} className=\"plenary-form__field plenary-form__field--unsupported\">\n <p className=\"plenary-form__notice\">\n “{f.name}” uses a field type the Form block does not support.\n </p>\n </div>\n );\n }\n const isCheckbox = f.type === \"boolean\";\n const label = (\n <label className=\"plenary-form__label\" htmlFor={isCheckbox ? undefined : id}>\n {isCheckbox && <FieldInput field={f} id={id} />}\n {f.name}\n {f.required ? \" *\" : \"\"}\n </label>\n );\n return (\n <div key={f.key} className=\"plenary-form__field\">\n {label}\n {!isCheckbox && <FieldInput field={f} id={id} />}\n {typeof f.ui?.help_text === \"string\" && f.ui.help_text !== \"\" && (\n <p className=\"plenary-form__help\">{f.ui.help_text as ReactNode}</p>\n )}\n {error?.field === f.key && (\n <p className=\"plenary-form__error\" role=\"alert\">\n {error.message}\n </p>\n )}\n </div>\n );\n })}\n {honeypot && (\n <div aria-hidden=\"true\" style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1, overflow: \"hidden\" }}>\n <label>\n Leave this field blank\n <input ref={honeypotRef} type=\"text\" name=\"website\" tabIndex={-1} autoComplete=\"off\" />\n </label>\n </div>\n )}\n {error && !error.field && (\n <p className=\"plenary-form__error\" role=\"alert\">\n {error.message}\n </p>\n )}\n <div className=\"plenary-form__actions\">\n <button type=\"submit\" className=\"plenary-form__submit\">\n {submitLabel || \"Submit\"}\n </button>\n </div>\n </fieldset>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/context.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
import { PlenaryClient, ClientConfig } from '@plenary/client';
|
|
4
|
-
|
|
5
|
-
interface PlenaryProviderProps {
|
|
6
|
-
/** An already-created client (client-rendered apps). */
|
|
7
|
-
client?: PlenaryClient;
|
|
8
|
-
/**
|
|
9
|
-
* Or the client config, from which the provider creates one. This is the
|
|
10
|
-
* RSC-safe path: a client instance cannot cross a server/client component
|
|
11
|
-
* boundary, but plain config can (the API key is a public delivery key, so
|
|
12
|
-
* shipping it to the browser is by design).
|
|
13
|
-
*/
|
|
14
|
-
config?: ClientConfig;
|
|
15
|
-
children?: ReactNode;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Provides a `PlenaryClient` to components that act at runtime in the browser —
|
|
19
|
-
* today the built-in Form block, which submits through it. Rendering content
|
|
20
|
-
* does not require the provider; only interactive blocks do.
|
|
21
|
-
*/
|
|
22
|
-
declare function PlenaryProvider({ client, config, children }: PlenaryProviderProps): react.JSX.Element;
|
|
23
|
-
/** The nearest provided client, or `null` outside a {@link PlenaryProvider}. */
|
|
24
|
-
declare function usePlenaryClient(): PlenaryClient | null;
|
|
25
|
-
|
|
26
|
-
export { PlenaryProvider, type PlenaryProviderProps, usePlenaryClient };
|