@plenary/react 0.2.1 → 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/index.d.ts CHANGED
@@ -5,11 +5,8 @@ export { Section, SectionProps } from './components/Section.js';
5
5
  export { Text, TextProps } from './components/Text.js';
6
6
  export { Image, ImageProps } from './components/Image.js';
7
7
  export { Video, VideoProps } from './components/Video.js';
8
- export { Form, FormProps } from './components/Form.js';
9
8
  export { Placeholder, PlaceholderProps } from './components/Placeholder.js';
10
9
  export { MediaValue, ResolvedMedia, resolveMedia, toResolvedMedia } from './media.js';
11
- export { FormValue, ResolvedForm, toResolvedForm } from './form.js';
12
- export { PlenaryProvider, PlenaryProviderProps, usePlenaryClient } from './context.js';
13
10
  export { PlenaryComponent, RegisteredComponent, RenderContext, SlotProps } from './types.js';
14
11
  import 'react';
15
12
  import '@plenary/client';
package/dist/index.js CHANGED
@@ -17,25 +17,19 @@ import { Section } from "./components/Section.js";
17
17
  import { Text } from "./components/Text.js";
18
18
  import { Image } from "./components/Image.js";
19
19
  import { Video } from "./components/Video.js";
20
- import { Form } from "./components/Form.js";
21
- import { Placeholder } from "./components/Placeholder.js";
20
+ import {
21
+ Placeholder
22
+ } from "./components/Placeholder.js";
22
23
  import {
23
24
  resolveMedia,
24
25
  toResolvedMedia
25
26
  } from "./media.js";
26
- import { toResolvedForm } from "./form.js";
27
- import {
28
- PlenaryProvider,
29
- usePlenaryClient
30
- } from "./context.js";
31
27
  export {
32
28
  Box,
33
- Form,
34
29
  Image,
35
30
  Placeholder,
36
31
  Plenary,
37
32
  PlenaryPage,
38
- PlenaryProvider,
39
33
  Registry,
40
34
  Section,
41
35
  Text,
@@ -48,8 +42,6 @@ export {
48
42
  renderDocument,
49
43
  renderDocumentSync,
50
44
  resolveMedia,
51
- toResolvedForm,
52
- toResolvedMedia,
53
- usePlenaryClient
45
+ toResolvedMedia
54
46
  };
55
47
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @plenary/react — the DOM renderer, component registry, and built-in\n * components (Box/Section/Text/Image/Video/Form) for rendering a Page's `dom`\n * tree. Custom components register on top of the built-ins; the preview bridge\n * lives in the `./bridge` export.\n */\n\nexport {\n PlenaryPage,\n renderDocument,\n renderDocumentSync,\n type PlenaryPageProps,\n type SyncRenderOptions,\n} from \"./render.js\";\nexport {\n Registry,\n registry,\n register,\n registerTheme,\n Plenary,\n getManifest,\n getTheme,\n} from \"./registry.js\";\nexport { Box, type BoxProps } from \"./components/Box.js\";\nexport { Section, type SectionProps } from \"./components/Section.js\";\nexport { Text, type TextProps } from \"./components/Text.js\";\nexport { Image, type ImageProps } from \"./components/Image.js\";\nexport { Video, type VideoProps } from \"./components/Video.js\";\nexport { Form, type FormProps } from \"./components/Form.js\";\nexport { Placeholder, type PlaceholderProps } from \"./components/Placeholder.js\";\nexport {\n resolveMedia,\n toResolvedMedia,\n type MediaValue,\n type ResolvedMedia,\n} from \"./media.js\";\nexport { toResolvedForm, type FormValue, type ResolvedForm } from \"./form.js\";\nexport {\n PlenaryProvider,\n usePlenaryClient,\n type PlenaryProviderProps,\n} from \"./context.js\";\nexport type {\n PlenaryComponent,\n RegisteredComponent,\n RenderContext,\n SlotProps,\n} from \"./types.js\";\n"],"mappings":"AAOA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAA0B;AACnC,SAAS,eAAkC;AAC3C,SAAS,YAA4B;AACrC,SAAS,aAA8B;AACvC,SAAS,aAA8B;AACvC,SAAS,YAA4B;AACrC,SAAS,mBAA0C;AACnD;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,sBAAyD;AAClE;AAAA,EACE;AAAA,EACA;AAAA,OAEK;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @plenary/react — the DOM renderer, component registry, and built-in\n * components (Box/Section/Text/Image/Video) for rendering a Page's `dom`\n * tree. Custom components register on top of the built-ins; the preview bridge\n * lives in the `./bridge` export.\n */\n\nexport {\n PlenaryPage,\n renderDocument,\n renderDocumentSync,\n type PlenaryPageProps,\n type SyncRenderOptions,\n} from \"./render.js\";\nexport {\n Registry,\n registry,\n register,\n registerTheme,\n Plenary,\n getManifest,\n getTheme,\n} from \"./registry.js\";\nexport { Box, type BoxProps } from \"./components/Box.js\";\nexport { Section, type SectionProps } from \"./components/Section.js\";\nexport { Text, type TextProps } from \"./components/Text.js\";\nexport { Image, type ImageProps } from \"./components/Image.js\";\nexport { Video, type VideoProps } from \"./components/Video.js\";\nexport {\n Placeholder,\n type PlaceholderProps,\n} from \"./components/Placeholder.js\";\nexport {\n resolveMedia,\n toResolvedMedia,\n type MediaValue,\n type ResolvedMedia,\n} from \"./media.js\";\nexport type {\n PlenaryComponent,\n RegisteredComponent,\n RenderContext,\n SlotProps,\n} from \"./types.js\";\n"],"mappings":"AAOA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAA0B;AACnC,SAAS,eAAkC;AAC3C,SAAS,YAA4B;AACrC,SAAS,aAA8B;AACvC,SAAS,aAA8B;AACvC;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;","names":[]}
package/dist/registry.js CHANGED
@@ -3,7 +3,6 @@ import { Section } from "./components/Section.js";
3
3
  import { Text } from "./components/Text.js";
4
4
  import { Image } from "./components/Image.js";
5
5
  import { Video } from "./components/Video.js";
6
- import { Form } from "./components/Form.js";
7
6
  const BUILTINS = [
8
7
  {
9
8
  component: Box,
@@ -97,13 +96,30 @@ const BUILTINS = [
97
96
  // The source file is served as uploaded — no transcoding — so the
98
97
  // picker pins to the library's video type. Alt/caption sub-fields are
99
98
  // hidden like the Image block's: the player has no use for them.
100
- { key: "media", type: "file", ui: { label: "Video", media_type: "video", show_alt_caption: false } },
101
- { key: "poster", type: "image", ui: { label: "Poster Image", show_alt_caption: false } },
99
+ {
100
+ key: "media",
101
+ type: "file",
102
+ ui: { label: "Video", media_type: "video", show_alt_caption: false }
103
+ },
104
+ {
105
+ key: "poster",
106
+ type: "image",
107
+ ui: { label: "Poster Image", show_alt_caption: false }
108
+ },
102
109
  { key: "controls", type: "boolean", defaultValue: true },
103
- { key: "autoplay", type: "boolean", ui: { label: "Autoplay (browsers require Muted)" } },
110
+ {
111
+ key: "autoplay",
112
+ type: "boolean",
113
+ ui: { label: "Autoplay (browsers require Muted)" }
114
+ },
104
115
  { key: "loop", type: "boolean" },
105
116
  { key: "muted", type: "boolean" },
106
- { key: "playsinline", type: "boolean", defaultValue: true, ui: { label: "Plays Inline" } },
117
+ {
118
+ key: "playsinline",
119
+ type: "boolean",
120
+ defaultValue: true,
121
+ ui: { label: "Plays Inline" }
122
+ },
107
123
  {
108
124
  key: "preload",
109
125
  type: "select",
@@ -119,48 +135,6 @@ const BUILTINS = [
119
135
  ],
120
136
  styles: ["size", "css"]
121
137
  }
122
- },
123
- {
124
- component: Form,
125
- registration: {
126
- key: "form",
127
- label: "Form",
128
- category: "Content",
129
- props: [
130
- { key: "form", type: "form", ui: { label: "Form" } },
131
- { key: "showTitle", type: "boolean", ui: { label: "Show Form Title" } },
132
- { key: "submitLabel", type: "text", defaultValue: "Submit", ui: { label: "Submit Button Label" } },
133
- {
134
- key: "onSuccess",
135
- type: "select",
136
- // Required: a non-required select offers an empty "None" choice,
137
- // which is meaningless here — the block always does one of the two.
138
- required: true,
139
- defaultValue: "message",
140
- ui: { label: "After Submit" },
141
- settings: {
142
- options: [
143
- { value: "message", label: "Show a success message" },
144
- { value: "redirect", label: "Redirect to a page" }
145
- ]
146
- }
147
- },
148
- {
149
- key: "successMessage",
150
- type: "text",
151
- defaultValue: "Thanks! Your submission has been received.",
152
- ui: { label: "Success Message", control: "textarea" },
153
- visibleWhen: { prop: "onSuccess", equals: "message" }
154
- },
155
- {
156
- key: "redirectUrl",
157
- type: "url",
158
- ui: { label: "Redirect URL" },
159
- visibleWhen: { prop: "onSuccess", equals: "redirect" }
160
- }
161
- ],
162
- styles: ["layout", "size", "typography", "css"]
163
- }
164
138
  }
165
139
  ];
166
140
  class Registry {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts"],"sourcesContent":["import type {\n ComponentRegistration,\n ManifestComponent,\n ThemeRegistration,\n} from \"@plenary/core\";\nimport type { PlenaryComponent, RegisteredComponent } from \"./types.js\";\nimport { Box } from \"./components/Box.js\";\nimport { Section } from \"./components/Section.js\";\nimport { Text } from \"./components/Text.js\";\nimport { Image } from \"./components/Image.js\";\nimport { Video } from \"./components/Video.js\";\nimport { Form } from \"./components/Form.js\";\n\n/** The built-in primitives, pre-registered so they behave like any custom component. */\nconst BUILTINS: { component: PlenaryComponent; registration: ComponentRegistration }[] = [\n {\n component: Box,\n registration: {\n key: \"box\",\n label: \"Box\",\n category: \"Layout\",\n props: [{ key: \"className\", type: \"text\" }],\n slots: [{ name: \"children\", label: \"Content\" }],\n styles: [\"layout\", \"size\", \"background\", \"css\"],\n },\n },\n {\n component: Section,\n registration: {\n key: \"section\",\n label: \"Section\",\n category: \"Layout\",\n // Column slots (\"0\"…\"n-1\") are dynamic, driven by `columns`, and managed\n // by the built-in — so they are not declared statically here.\n props: [\n { key: \"columns\", type: \"number\", defaultValue: 1 },\n { key: \"gap\", type: \"number\", defaultValue: 16 },\n { key: \"className\", type: \"text\" },\n ],\n styles: [\"size\", \"background\", \"css\"],\n },\n },\n {\n component: Text,\n registration: {\n key: \"text\",\n label: \"Text\",\n category: \"Content\",\n props: [\n { key: \"content\", type: \"rich_text\" },\n {\n key: \"as\",\n type: \"select\",\n defaultValue: \"p\",\n // Options use the server's select field-settings shape ({value,label})\n // so the console's select input can drive them directly.\n settings: {\n options: [\n { value: \"p\", label: \"Paragraph\" },\n { value: \"h1\", label: \"Heading 1\" },\n { value: \"h2\", label: \"Heading 2\" },\n { value: \"h3\", label: \"Heading 3\" },\n { value: \"h4\", label: \"Heading 4\" },\n { value: \"h5\", label: \"Heading 5\" },\n { value: \"h6\", label: \"Heading 6\" },\n { value: \"span\", label: \"Span\" },\n { value: \"code\", label: \"Code\" },\n ],\n },\n },\n ],\n styles: [\"size\", \"typography\", \"css\"],\n },\n },\n {\n component: Image,\n registration: {\n key: \"image\",\n label: \"Image\",\n category: \"Content\",\n props: [\n // Alt is a block prop (below), so the media picker shows only the\n // asset itself — not its own alt/caption sub-fields.\n { key: \"media\", type: \"image\", ui: { show_alt_caption: false } },\n { key: \"alt\", type: \"text\", ui: { label: \"Alt Text\" } },\n // A one-off variant spec for this placement. The server enqueues the\n // matching variant on save and hydrates `media.variant_url` on read;\n // with no dimensions set, the original renders. Labels say \"output\"\n // (pixels served) to keep these apart from the Styles tab's layout Size.\n { key: \"width\", type: \"number\", ui: { label: \"Max Width (px)\" } },\n { key: \"height\", type: \"number\", ui: { label: \"Max Height (px)\" } },\n { key: \"quality\", type: \"number\", ui: { label: \"Quality (%)\" } },\n { key: \"crop\", type: \"boolean\", ui: { label: \"Crop to fill\" } },\n ],\n styles: [\"size\", \"css\"],\n },\n },\n {\n component: Video,\n registration: {\n key: \"video\",\n label: \"Video\",\n category: \"Content\",\n props: [\n // The source file is served as uploaded — no transcoding — so the\n // picker pins to the library's video type. Alt/caption sub-fields are\n // hidden like the Image block's: the player has no use for them.\n { key: \"media\", type: \"file\", ui: { label: \"Video\", media_type: \"video\", show_alt_caption: false } },\n { key: \"poster\", type: \"image\", ui: { label: \"Poster Image\", show_alt_caption: false } },\n { key: \"controls\", type: \"boolean\", defaultValue: true },\n { key: \"autoplay\", type: \"boolean\", ui: { label: \"Autoplay (browsers require Muted)\" } },\n { key: \"loop\", type: \"boolean\" },\n { key: \"muted\", type: \"boolean\" },\n { key: \"playsinline\", type: \"boolean\", defaultValue: true, ui: { label: \"Plays Inline\" } },\n {\n key: \"preload\",\n type: \"select\",\n defaultValue: \"metadata\",\n settings: {\n options: [\n { value: \"none\", label: \"None\" },\n { value: \"metadata\", label: \"Metadata\" },\n { value: \"auto\", label: \"Auto\" },\n ],\n },\n },\n ],\n styles: [\"size\", \"css\"],\n },\n },\n {\n component: Form,\n registration: {\n key: \"form\",\n label: \"Form\",\n category: \"Content\",\n props: [\n { key: \"form\", type: \"form\", ui: { label: \"Form\" } },\n { key: \"showTitle\", type: \"boolean\", ui: { label: \"Show Form Title\" } },\n { key: \"submitLabel\", type: \"text\", defaultValue: \"Submit\", ui: { label: \"Submit Button Label\" } },\n {\n key: \"onSuccess\",\n type: \"select\",\n // Required: a non-required select offers an empty \"None\" choice,\n // which is meaningless here — the block always does one of the two.\n required: true,\n defaultValue: \"message\",\n ui: { label: \"After Submit\" },\n settings: {\n options: [\n { value: \"message\", label: \"Show a success message\" },\n { value: \"redirect\", label: \"Redirect to a page\" },\n ],\n },\n },\n {\n key: \"successMessage\",\n type: \"text\",\n defaultValue: \"Thanks! Your submission has been received.\",\n ui: { label: \"Success Message\", control: \"textarea\" },\n visibleWhen: { prop: \"onSuccess\", equals: \"message\" },\n },\n {\n key: \"redirectUrl\",\n type: \"url\",\n ui: { label: \"Redirect URL\" },\n visibleWhen: { prop: \"onSuccess\", equals: \"redirect\" },\n },\n ],\n styles: [\"layout\", \"size\", \"typography\", \"css\"],\n },\n },\n];\n\n/**\n * The module-singleton component registry. Because it is a shared module, both\n * the render path (SSR + client) and the preview bridge see the same\n * registrations — the site's registry file is imported wherever either is used.\n */\nexport class Registry {\n private components = new Map<string, RegisteredComponent>();\n private theme: ThemeRegistration | undefined;\n\n constructor() {\n for (const b of BUILTINS) this.register(b.component, b.registration);\n }\n\n /** Register a component (built-in or custom) under its registration key. */\n register(component: PlenaryComponent, registration: ComponentRegistration): this {\n this.components.set(registration.key, { component, registration });\n return this;\n }\n\n /**\n * Declare the site's design tokens. Each token mirrors a CSS custom property\n * the site's stylesheet defines (`--<name>`); the editor stores references\n * (`var(--<name>)`), so the tokens must actually exist in the site's CSS for\n * styled pages to render.\n */\n registerTheme(theme: ThemeRegistration): this {\n this.theme = theme;\n return this;\n }\n\n getTheme(): ThemeRegistration | undefined {\n return this.theme;\n }\n\n get(key: string): RegisteredComponent | undefined {\n return this.components.get(key);\n }\n\n all(): RegisteredComponent[] {\n return [...this.components.values()];\n }\n\n /** The full component manifest announced to the console over the bridge. */\n manifest(): ManifestComponent[] {\n return this.all().map(({ registration: r }) => ({\n name: r.key,\n label: r.label,\n icon: r.icon,\n props: r.props ?? [],\n slots: r.slots ?? [],\n category: r.category,\n styles: r.styles,\n }));\n }\n}\n\nexport const registry = new Registry();\n\n/** Register a component so the dom tree may reference it by key. */\nexport function register(component: PlenaryComponent, registration: ComponentRegistration): Registry {\n return registry.register(component, registration);\n}\n\n/** Declare the site's design tokens (see Registry.registerTheme). */\nexport function registerTheme(theme: ThemeRegistration): Registry {\n return registry.registerTheme(theme);\n}\n\n/** Namespace mirroring the design brief's `Plenary.register(...)` surface. */\nexport const Plenary = { register, registerTheme };\n\n/** The manifest of every registered component (built-ins + custom). */\nexport function getManifest(): ManifestComponent[] {\n return registry.manifest();\n}\n\n/** The site's registered design tokens, if any. */\nexport function getTheme(): ThemeRegistration | undefined {\n return registry.getTheme();\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/registry.ts"],"sourcesContent":["import type {\n ComponentRegistration,\n ManifestComponent,\n ThemeRegistration,\n} from \"@plenary/core\";\nimport type { PlenaryComponent, RegisteredComponent } from \"./types.js\";\nimport { Box } from \"./components/Box.js\";\nimport { Section } from \"./components/Section.js\";\nimport { Text } from \"./components/Text.js\";\nimport { Image } from \"./components/Image.js\";\nimport { Video } from \"./components/Video.js\";\n\n/** The built-in primitives, pre-registered so they behave like any custom component. */\nconst BUILTINS: {\n component: PlenaryComponent;\n registration: ComponentRegistration;\n}[] = [\n {\n component: Box,\n registration: {\n key: \"box\",\n label: \"Box\",\n category: \"Layout\",\n props: [{ key: \"className\", type: \"text\" }],\n slots: [{ name: \"children\", label: \"Content\" }],\n styles: [\"layout\", \"size\", \"background\", \"css\"],\n },\n },\n {\n component: Section,\n registration: {\n key: \"section\",\n label: \"Section\",\n category: \"Layout\",\n // Column slots (\"0\"…\"n-1\") are dynamic, driven by `columns`, and managed\n // by the built-in — so they are not declared statically here.\n props: [\n { key: \"columns\", type: \"number\", defaultValue: 1 },\n { key: \"gap\", type: \"number\", defaultValue: 16 },\n { key: \"className\", type: \"text\" },\n ],\n styles: [\"size\", \"background\", \"css\"],\n },\n },\n {\n component: Text,\n registration: {\n key: \"text\",\n label: \"Text\",\n category: \"Content\",\n props: [\n { key: \"content\", type: \"rich_text\" },\n {\n key: \"as\",\n type: \"select\",\n defaultValue: \"p\",\n // Options use the server's select field-settings shape ({value,label})\n // so the console's select input can drive them directly.\n settings: {\n options: [\n { value: \"p\", label: \"Paragraph\" },\n { value: \"h1\", label: \"Heading 1\" },\n { value: \"h2\", label: \"Heading 2\" },\n { value: \"h3\", label: \"Heading 3\" },\n { value: \"h4\", label: \"Heading 4\" },\n { value: \"h5\", label: \"Heading 5\" },\n { value: \"h6\", label: \"Heading 6\" },\n { value: \"span\", label: \"Span\" },\n { value: \"code\", label: \"Code\" },\n ],\n },\n },\n ],\n styles: [\"size\", \"typography\", \"css\"],\n },\n },\n {\n component: Image,\n registration: {\n key: \"image\",\n label: \"Image\",\n category: \"Content\",\n props: [\n // Alt is a block prop (below), so the media picker shows only the\n // asset itself — not its own alt/caption sub-fields.\n { key: \"media\", type: \"image\", ui: { show_alt_caption: false } },\n { key: \"alt\", type: \"text\", ui: { label: \"Alt Text\" } },\n // A one-off variant spec for this placement. The server enqueues the\n // matching variant on save and hydrates `media.variant_url` on read;\n // with no dimensions set, the original renders. Labels say \"output\"\n // (pixels served) to keep these apart from the Styles tab's layout Size.\n { key: \"width\", type: \"number\", ui: { label: \"Max Width (px)\" } },\n { key: \"height\", type: \"number\", ui: { label: \"Max Height (px)\" } },\n { key: \"quality\", type: \"number\", ui: { label: \"Quality (%)\" } },\n { key: \"crop\", type: \"boolean\", ui: { label: \"Crop to fill\" } },\n ],\n styles: [\"size\", \"css\"],\n },\n },\n {\n component: Video,\n registration: {\n key: \"video\",\n label: \"Video\",\n category: \"Content\",\n props: [\n // The source file is served as uploaded — no transcoding — so the\n // picker pins to the library's video type. Alt/caption sub-fields are\n // hidden like the Image block's: the player has no use for them.\n {\n key: \"media\",\n type: \"file\",\n ui: { label: \"Video\", media_type: \"video\", show_alt_caption: false },\n },\n {\n key: \"poster\",\n type: \"image\",\n ui: { label: \"Poster Image\", show_alt_caption: false },\n },\n { key: \"controls\", type: \"boolean\", defaultValue: true },\n {\n key: \"autoplay\",\n type: \"boolean\",\n ui: { label: \"Autoplay (browsers require Muted)\" },\n },\n { key: \"loop\", type: \"boolean\" },\n { key: \"muted\", type: \"boolean\" },\n {\n key: \"playsinline\",\n type: \"boolean\",\n defaultValue: true,\n ui: { label: \"Plays Inline\" },\n },\n {\n key: \"preload\",\n type: \"select\",\n defaultValue: \"metadata\",\n settings: {\n options: [\n { value: \"none\", label: \"None\" },\n { value: \"metadata\", label: \"Metadata\" },\n { value: \"auto\", label: \"Auto\" },\n ],\n },\n },\n ],\n styles: [\"size\", \"css\"],\n },\n },\n];\n\n/**\n * The module-singleton component registry. Because it is a shared module, both\n * the render path (SSR + client) and the preview bridge see the same\n * registrations — the site's registry file is imported wherever either is used.\n */\nexport class Registry {\n private components = new Map<string, RegisteredComponent>();\n private theme: ThemeRegistration | undefined;\n\n constructor() {\n for (const b of BUILTINS) this.register(b.component, b.registration);\n }\n\n /** Register a component (built-in or custom) under its registration key. */\n register(\n component: PlenaryComponent,\n registration: ComponentRegistration,\n ): this {\n this.components.set(registration.key, { component, registration });\n return this;\n }\n\n /**\n * Declare the site's design tokens. Each token mirrors a CSS custom property\n * the site's stylesheet defines (`--<name>`); the editor stores references\n * (`var(--<name>)`), so the tokens must actually exist in the site's CSS for\n * styled pages to render.\n */\n registerTheme(theme: ThemeRegistration): this {\n this.theme = theme;\n return this;\n }\n\n getTheme(): ThemeRegistration | undefined {\n return this.theme;\n }\n\n get(key: string): RegisteredComponent | undefined {\n return this.components.get(key);\n }\n\n all(): RegisteredComponent[] {\n return [...this.components.values()];\n }\n\n /** The full component manifest announced to the console over the bridge. */\n manifest(): ManifestComponent[] {\n return this.all().map(({ registration: r }) => ({\n name: r.key,\n label: r.label,\n icon: r.icon,\n props: r.props ?? [],\n slots: r.slots ?? [],\n category: r.category,\n styles: r.styles,\n }));\n }\n}\n\nexport const registry = new Registry();\n\n/** Register a component so the dom tree may reference it by key. */\nexport function register(\n component: PlenaryComponent,\n registration: ComponentRegistration,\n): Registry {\n return registry.register(component, registration);\n}\n\n/** Declare the site's design tokens (see Registry.registerTheme). */\nexport function registerTheme(theme: ThemeRegistration): Registry {\n return registry.registerTheme(theme);\n}\n\n/** Namespace mirroring the design brief's `Plenary.register(...)` surface. */\nexport const Plenary = { register, registerTheme };\n\n/** The manifest of every registered component (built-ins + custom). */\nexport function getManifest(): ManifestComponent[] {\n return registry.manifest();\n}\n\n/** The site's registered design tokens, if any. */\nexport function getTheme(): ThemeRegistration | undefined {\n return registry.getTheme();\n}\n"],"mappings":"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","names":[]}
package/dist/render.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { PlenaryClient, MediaDetail, PublicFormDefinition } from '@plenary/client';
2
+ import { PlenaryClient, MediaDetail } from '@plenary/client';
3
3
  import { PlenaryDocument } from '@plenary/core';
4
4
  import { RenderContext } from './types.js';
5
5
 
@@ -9,7 +9,6 @@ interface SyncRenderOptions {
9
9
  /** Media ids known to be deleted. The console marks preview values instead. */
10
10
  missingMedia?: Set<string>;
11
11
  /** Pre-fetched form definitions, keyed by form key (empty resolves nothing). */
12
- formDefinitions?: Map<string, PublicFormDefinition>;
13
12
  /** Wrap nodes with `data-plenary-id` markers (for the preview bridge). */
14
13
  annotate?: boolean;
15
14
  /** Per-node remount epochs — see RenderOptions.remountKeys. */
@@ -29,18 +28,17 @@ interface SyncRenderOptions {
29
28
  */
30
29
  declare function renderDocumentSync(document: PlenaryDocument, opts?: SyncRenderOptions): ReactNode;
31
30
  /**
32
- * Render a whole document to a React tree. Resolves media and form definitions
33
- * first (batched async passes), then walks the tree synchronously. Wrapped in
34
- * Suspense so lazy (`React.lazy` / `next/dynamic`) custom components are
35
- * SSR-safe.
31
+ * Render a whole document to a React tree. Resolves media first (one batched
32
+ * async pass), then walks the tree synchronously. Wrapped in Suspense so lazy
33
+ * (`React.lazy` / `next/dynamic`) custom components are SSR-safe.
36
34
  */
37
35
  declare function renderDocument(document: PlenaryDocument, ctx?: RenderContext): Promise<ReactNode>;
38
36
  interface PlenaryPageProps {
39
37
  document: PlenaryDocument;
40
- /** Client used to resolve media and form props (optional). */
38
+ /** Client used to resolve media props (optional). */
41
39
  client?: PlenaryClient;
42
40
  }
43
41
  /** Renders a Page's `dom` tree. An async server component (SSR-friendly). */
44
- declare function PlenaryPage({ document, client }: PlenaryPageProps): Promise<ReactNode>;
42
+ declare function PlenaryPage({ document, client, }: PlenaryPageProps): Promise<ReactNode>;
45
43
 
46
44
  export { PlenaryPage, type PlenaryPageProps, type SyncRenderOptions, renderDocument, renderDocumentSync };
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)) walkNodes(children, visit);
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
- async function resolveFormDefinitions(document, client) {
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("div", { "data-plenary-id": id, style: { display: "contents" }, children: element }, nodeKey(id, opts));
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(node.id, /* @__PURE__ */ jsx(Placeholder, { component: node.component }, key), opts);
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(node.id, createElement(Component, { ...props, key }, children), opts);
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(document, resolveCutoffs(registry.getTheme()));
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("style", { "data-plenary-styles": "", dangerouslySetInnerHTML: { __html: css } }) : null,
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 [media, formDefinitions] = await Promise.all([
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({ document, client }) {
159
+ async function PlenaryPage({
160
+ document,
161
+ client
162
+ }) {
172
163
  return renderDocument(document, { client });
173
164
  }
174
165
  export {
@@ -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.1",
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.1",
24
- "@plenary/core": "0.2.1"
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 };
@@ -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 };
package/dist/context.js DELETED
@@ -1,20 +0,0 @@
1
- "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useMemo } from "react";
4
- import { createClient } from "@plenary/client";
5
- const PlenaryClientContext = createContext(null);
6
- function PlenaryProvider({ client, config, children }) {
7
- const value = useMemo(
8
- () => client ?? (config ? createClient(config) : null),
9
- [client, config?.baseUrl, config?.siteKey, config?.apiKey, config?.locale, config?.fetch]
10
- );
11
- return /* @__PURE__ */ jsx(PlenaryClientContext.Provider, { value, children });
12
- }
13
- function usePlenaryClient() {
14
- return useContext(PlenaryClientContext);
15
- }
16
- export {
17
- PlenaryProvider,
18
- usePlenaryClient
19
- };
20
- //# sourceMappingURL=context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/context.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport { createClient, type ClientConfig, type PlenaryClient } from \"@plenary/client\";\n\nconst PlenaryClientContext = createContext<PlenaryClient | null>(null);\n\nexport interface PlenaryProviderProps {\n /** An already-created client (client-rendered apps). */\n client?: PlenaryClient;\n /**\n * Or the client config, from which the provider creates one. This is the\n * RSC-safe path: a client instance cannot cross a server/client component\n * boundary, but plain config can (the API key is a public delivery key, so\n * shipping it to the browser is by design).\n */\n config?: ClientConfig;\n children?: ReactNode;\n}\n\n/**\n * Provides a `PlenaryClient` to components that act at runtime in the browser —\n * today the built-in Form block, which submits through it. Rendering content\n * does not require the provider; only interactive blocks do.\n */\nexport function PlenaryProvider({ client, config, children }: PlenaryProviderProps) {\n // Keyed on config fields, not object identity — an inline config literal\n // must not rebuild the client (and re-render every consumer) each render.\n const value = useMemo(\n () => client ?? (config ? createClient(config) : null),\n [client, config?.baseUrl, config?.siteKey, config?.apiKey, config?.locale, config?.fetch],\n );\n return <PlenaryClientContext.Provider value={value}>{children}</PlenaryClientContext.Provider>;\n}\n\n/** The nearest provided client, or `null` outside a {@link PlenaryProvider}. */\nexport function usePlenaryClient(): PlenaryClient | null {\n return useContext(PlenaryClientContext);\n}\n"],"mappings":";AAgCS;AA9BT,SAAS,eAAe,YAAY,eAA+B;AACnE,SAAS,oBAA2D;AAEpE,MAAM,uBAAuB,cAAoC,IAAI;AAoB9D,SAAS,gBAAgB,EAAE,QAAQ,QAAQ,SAAS,GAAyB;AAGlF,QAAM,QAAQ;AAAA,IACZ,MAAM,WAAW,SAAS,aAAa,MAAM,IAAI;AAAA,IACjD,CAAC,QAAQ,QAAQ,SAAS,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,KAAK;AAAA,EAC1F;AACA,SAAO,oBAAC,qBAAqB,UAArB,EAA8B,OAAe,UAAS;AAChE;AAGO,SAAS,mBAAyC;AACvD,SAAO,WAAW,oBAAoB;AACxC;","names":[]}
package/dist/form.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import { PublicFormDefinition } from '@plenary/client';
2
-
3
- /** The raw value a `form` prop holds in the dom: a pointer to a form by key. */
4
- interface FormValue {
5
- form_key?: string;
6
- /** Present when the value was already resolved (the preview inlines it). */
7
- definition?: PublicFormDefinition | null;
8
- }
9
- /**
10
- * The shape a component sees after resolution. `definition` is `null` when the
11
- * form could not be fetched (deleted, inactive, not on this site, no client).
12
- */
13
- interface ResolvedForm {
14
- form_key: string;
15
- definition: PublicFormDefinition | null;
16
- }
17
- /**
18
- * Merge a form prop value with its (optional) fetched definition into the
19
- * resolved shape a component sees. Pure and synchronous — the renderer calls it
20
- * after a batch definition fetch. A value that already carries a definition
21
- * (the preview inlines one) is used as-is.
22
- */
23
- declare function toResolvedForm(value: FormValue | null | undefined, definition?: PublicFormDefinition | null): ResolvedForm | null;
24
-
25
- export { type FormValue, type ResolvedForm, toResolvedForm };
package/dist/form.js DELETED
@@ -1,11 +0,0 @@
1
- function toResolvedForm(value, definition) {
2
- if (!value || !value.form_key) return null;
3
- return {
4
- form_key: value.form_key,
5
- definition: value.definition !== void 0 ? value.definition : definition ?? null
6
- };
7
- }
8
- export {
9
- toResolvedForm
10
- };
11
- //# sourceMappingURL=form.js.map
package/dist/form.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/form.ts"],"sourcesContent":["import type { PublicFormDefinition } from \"@plenary/client\";\n\n/** The raw value a `form` prop holds in the dom: a pointer to a form by key. */\nexport interface FormValue {\n form_key?: string;\n /** Present when the value was already resolved (the preview inlines it). */\n definition?: PublicFormDefinition | null;\n}\n\n/**\n * The shape a component sees after resolution. `definition` is `null` when the\n * form could not be fetched (deleted, inactive, not on this site, no client).\n */\nexport interface ResolvedForm {\n form_key: string;\n definition: PublicFormDefinition | null;\n}\n\n/**\n * Merge a form prop value with its (optional) fetched definition into the\n * resolved shape a component sees. Pure and synchronous — the renderer calls it\n * after a batch definition fetch. A value that already carries a definition\n * (the preview inlines one) is used as-is.\n */\nexport function toResolvedForm(\n value: FormValue | null | undefined,\n definition?: PublicFormDefinition | null,\n): ResolvedForm | null {\n if (!value || !value.form_key) return null;\n return {\n form_key: value.form_key,\n definition: value.definition !== undefined ? value.definition : (definition ?? null),\n };\n}\n"],"mappings":"AAwBO,SAAS,eACd,OACA,YACqB;AACrB,MAAI,CAAC,SAAS,CAAC,MAAM,SAAU,QAAO;AACtC,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,YAAY,MAAM,eAAe,SAAY,MAAM,aAAc,cAAc;AAAA,EACjF;AACF;","names":[]}