@prenta/sections-react 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +1037 -0
  2. package/LICENSE +21 -0
  3. package/README.md +36 -0
  4. package/dist/PrentaForm.d.ts +22 -0
  5. package/dist/PrentaForm.d.ts.map +1 -0
  6. package/dist/PrentaForm.js +203 -0
  7. package/dist/PrentaForm.js.map +1 -0
  8. package/dist/PrentaFormHtml.d.ts +19 -0
  9. package/dist/PrentaFormHtml.d.ts.map +1 -0
  10. package/dist/PrentaFormHtml.js +18 -0
  11. package/dist/PrentaFormHtml.js.map +1 -0
  12. package/dist/Sections.d.ts +40 -0
  13. package/dist/Sections.d.ts.map +1 -0
  14. package/dist/Sections.js +80 -0
  15. package/dist/Sections.js.map +1 -0
  16. package/dist/SiteNav.d.ts +38 -0
  17. package/dist/SiteNav.d.ts.map +1 -0
  18. package/dist/SiteNav.js +38 -0
  19. package/dist/SiteNav.js.map +1 -0
  20. package/dist/attribution.d.ts +31 -0
  21. package/dist/attribution.d.ts.map +1 -0
  22. package/dist/attribution.js +66 -0
  23. package/dist/attribution.js.map +1 -0
  24. package/dist/components/WidgetEmbedSection.d.ts +9 -0
  25. package/dist/components/WidgetEmbedSection.d.ts.map +1 -0
  26. package/dist/components/WidgetEmbedSection.js +113 -0
  27. package/dist/components/WidgetEmbedSection.js.map +1 -0
  28. package/dist/components/defaults.d.ts +34 -0
  29. package/dist/components/defaults.d.ts.map +1 -0
  30. package/dist/components/defaults.js +170 -0
  31. package/dist/components/defaults.js.map +1 -0
  32. package/dist/forms.d.ts +103 -0
  33. package/dist/forms.d.ts.map +1 -0
  34. package/dist/forms.js +217 -0
  35. package/dist/forms.js.map +1 -0
  36. package/dist/image.d.ts +17 -0
  37. package/dist/image.d.ts.map +1 -0
  38. package/dist/image.js +14 -0
  39. package/dist/image.js.map +1 -0
  40. package/dist/index.d.ts +51 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +44 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/lcp.d.ts +30 -0
  45. package/dist/lcp.d.ts.map +1 -0
  46. package/dist/lcp.js +34 -0
  47. package/dist/lcp.js.map +1 -0
  48. package/dist/primitives.d.ts +14 -0
  49. package/dist/primitives.d.ts.map +1 -0
  50. package/dist/primitives.js +18 -0
  51. package/dist/primitives.js.map +1 -0
  52. package/dist/registry.d.ts +40 -0
  53. package/dist/registry.d.ts.map +1 -0
  54. package/dist/registry.js +62 -0
  55. package/dist/registry.js.map +1 -0
  56. package/dist/sizes.d.ts +30 -0
  57. package/dist/sizes.d.ts.map +1 -0
  58. package/dist/sizes.js +34 -0
  59. package/dist/sizes.js.map +1 -0
  60. package/dist/testing.d.ts +95 -0
  61. package/dist/testing.d.ts.map +1 -0
  62. package/dist/testing.js +159 -0
  63. package/dist/testing.js.map +1 -0
  64. package/dist/types.d.ts +130 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +2 -0
  67. package/dist/types.js.map +1 -0
  68. package/dist/utils.d.ts +21 -0
  69. package/dist/utils.d.ts.map +1 -0
  70. package/dist/utils.js +65 -0
  71. package/dist/utils.js.map +1 -0
  72. package/dist/webmcp.d.ts +6 -0
  73. package/dist/webmcp.d.ts.map +1 -0
  74. package/dist/webmcp.js +18 -0
  75. package/dist/webmcp.js.map +1 -0
  76. package/package.json +69 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sizes.d.ts","sourceRoot":"","sources":["../src/sizes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,oCAAoC,CAAA;AAEpE;;;GAGG;AACH,eAAO,MAAM,aAAa,mCAAmC,CAAA;AAE7D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIpD"}
package/dist/sizes.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Responsive `sizes` helpers.
3
+ *
4
+ * The `sizes` attribute tells the browser how wide an image will render at each
5
+ * breakpoint so it can pick the smallest sufficient `srcset` candidate. Getting
6
+ * it wrong (or omitting it) makes mobile silently over-download the desktop-
7
+ * sized image — one of the most common, invisible LCP regressions.
8
+ *
9
+ * These constants/helpers encode the layout each built-in section renders at, so
10
+ * the right `sizes` ships by default without each renderer hand-rolling it.
11
+ */
12
+ /** Full-bleed image (hero, overlay header): one column at every width. */
13
+ export const FULL_WIDTH_SIZES = '100vw';
14
+ /**
15
+ * Centered content image capped at a max width (e.g. the standard post header
16
+ * featured image, rendered inside `max-w-4xl` = 896px).
17
+ */
18
+ export const CONTENT_WIDTH_SIZES = '(max-width: 896px) 100vw, 896px';
19
+ /**
20
+ * Feature section image: stacks full-width on mobile, ~half the row on `md+`
21
+ * (two-column grid).
22
+ */
23
+ export const FEATURE_SIZES = '(max-width: 768px) 100vw, 50vw';
24
+ /**
25
+ * Gallery cell `sizes` for a given column count: full-width on small screens,
26
+ * an even fraction of the viewport at the grid breakpoint. Clamps the column
27
+ * count to a sane range so a bad value can't produce a nonsense hint.
28
+ */
29
+ export function gallerySizes(columns) {
30
+ const cols = Number.isFinite(columns) ? Math.min(Math.max(Math.round(columns), 1), 6) : 3;
31
+ const pct = Math.round(100 / cols);
32
+ return `(max-width: 640px) 100vw, ${pct}vw`;
33
+ }
34
+ //# sourceMappingURL=sizes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sizes.js","sourceRoot":"","sources":["../src/sizes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,iCAAiC,CAAA;AAEpE;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,gCAAgC,CAAA;AAE7D;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACzF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;IAClC,OAAO,6BAA6B,GAAG,IAAI,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,95 @@
1
+ import type { PostContext, PublicSection, RenderImage, RenderOptions, SectionRendererMap } from './types.js';
2
+ /**
3
+ * Render options that mirror cms-admin `ADMIN_RENDER_OPTIONS`: identity
4
+ * sanitizer (rich text is sanitized on write) and a plain lazy `<img>` (no
5
+ * next/image pipeline inside the editor canvas).
6
+ */
7
+ export declare const CANVAS_RENDER_OPTIONS: RenderOptions;
8
+ /**
9
+ * Normalize SSR HTML for stable equality across public vs canvas image
10
+ * pipelines. Strips whitespace and attributes that legitimately differ between
11
+ * `defaultRenderImage` and canvas plain `<img>` (loading / fetchpriority /
12
+ * decoding / sizes / LCP preload imageSizes) so structural drift still fails.
13
+ */
14
+ export declare function normalizeSectionHtml(html: string): string;
15
+ export interface SectionRenderParityOptions {
16
+ /** Per-request post context (post-oriented sections). */
17
+ ctx?: PostContext;
18
+ /** Shared renderer map (the same map AdminRoot + PageSections must use). */
19
+ renderers?: SectionRendererMap;
20
+ /** Extra / override options for the public orchestrator path. */
21
+ publicOptions?: RenderOptions;
22
+ /** Extra / override options for the canvas orchestrator path. */
23
+ canvasOptions?: RenderOptions;
24
+ /** Custom normalizer; defaults to {@link normalizeSectionHtml}. */
25
+ normalize?: (html: string) => string;
26
+ }
27
+ /** Public orchestrator path — Astro/Next site via `<Sections>`. */
28
+ export declare function renderPublicSectionsHtml(sections: PublicSection[], opts?: {
29
+ ctx?: PostContext;
30
+ renderers?: SectionRendererMap;
31
+ options?: RenderOptions;
32
+ }): string;
33
+ /**
34
+ * Canvas orchestrator path — same `<Sections>` tree with canvas-equivalent
35
+ * options (mirrors how custom kits receive `ADMIN_RENDER_OPTIONS`).
36
+ */
37
+ export declare function renderCanvasSectionsHtml(sections: PublicSection[], opts?: {
38
+ ctx?: PostContext;
39
+ renderers?: SectionRendererMap;
40
+ options?: RenderOptions;
41
+ }): string;
42
+ /**
43
+ * Assert that public and canvas orchestrators emit the same normalized tree
44
+ * for the given sections (+ optional shared renderer map).
45
+ *
46
+ * Throws with both HTML snippets on mismatch so CI failures are actionable.
47
+ */
48
+ export declare function assertSectionRenderParity(sections: PublicSection[], opts?: SectionRenderParityOptions): void;
49
+ export interface SectionCoverageOptions {
50
+ /**
51
+ * Declared section types — the `sections.types` array from `prenta.config.ts`
52
+ * (objects with an `id`) or bare id strings.
53
+ */
54
+ declared: ReadonlyArray<string | {
55
+ id: string;
56
+ }>;
57
+ /** The shared renderer map (public `<Sections>` + `<AdminRoot sectionRenderers>`). */
58
+ renderers: SectionRendererMap;
59
+ /**
60
+ * Ids that intentionally have no renderer — data-only types whose content is
61
+ * consumed by a code-owned template. Every entry must exist in `declared`
62
+ * and must NOT have a renderer; a stale entry fails so the opt-out list
63
+ * stays honest instead of silently masking real gaps.
64
+ */
65
+ allowDataOnly?: readonly string[];
66
+ }
67
+ /**
68
+ * Assert the three-layer section contract agrees: every declared `sectionType`
69
+ * has a renderer (or is an explicit data-only opt-out), and every renderer
70
+ * corresponds to a declared type — so renaming a schema id and leaving the old
71
+ * renderer behind fails instead of shipping Structure view placeholders.
72
+ *
73
+ * This is the check the docs call `verify:sections`. Wire it as a one-line
74
+ * test against the SAME map you pass to both `<Sections>` and
75
+ * `<AdminRoot sectionRenderers>`:
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { assertSectionCoverage } from '@prenta/sections-react/testing'
80
+ * import { marketingSectionTypes } from '../sections/types'
81
+ * import { sectionRenderers } from '../sections/register'
82
+ *
83
+ * assertSectionCoverage({
84
+ * declared: marketingSectionTypes,
85
+ * renderers: sectionRenderers,
86
+ * allowDataOnly: ['careers-listing'], // consumed by a code-owned template
87
+ * })
88
+ * ```
89
+ *
90
+ * @throws Error listing every problem at once so CI failures are actionable.
91
+ */
92
+ export declare function assertSectionCoverage(options: SectionCoverageOptions): void;
93
+ /** Re-export for consumers that want to pin both sides to one image renderer. */
94
+ export type { RenderImage };
95
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAEnB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAcnC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,MAAM,WAAW,0BAA0B;IACzC,yDAAyD;IACzD,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,iEAAiE;IACjE,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,iEAAiE;IACjE,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,mEAAmE;IACnE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CACrC;AAED,mEAAmE;AACnE,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,aAAa,EAAE,EACzB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB,GACA,MAAM,CASR;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,aAAa,EAAE,EACzB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB,GACA,MAAM,CASR;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,aAAa,EAAE,EACzB,IAAI,CAAC,EAAE,0BAA0B,GAChC,IAAI,CA6BN;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC,MAAM,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChD,sFAAsF;IACtF,SAAS,EAAE,kBAAkB,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAsD3E;AAED,iFAAiF;AACjF,YAAY,EAAE,WAAW,EAAE,CAAA"}
@@ -0,0 +1,159 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Canvas ↔ public section-render parity helpers for Astro-split sites.
4
+ *
5
+ * In 100% Next, the editing canvas and the public page share one renderer →
6
+ * WYSIWYG is free. In the Astro split, the canvas (Next admin) and the public
7
+ * site (Astro) both orchestrate the same `sectionRenderers` map through
8
+ * `<Sections>` / `<AdminRoot sectionRenderers>` — this module is the contract
9
+ * test so those two paths can't drift into a WYSIWYG lie.
10
+ *
11
+ * Built-in admin chrome in cms-admin (HeroBannerSection, etc.) is intentionally
12
+ * out of scope — the harness covers the shared custom-kit path that both
13
+ * surfaces must keep identical.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { assertSectionRenderParity } from '@prenta/sections-react/testing'
18
+ * import { sectionRenderers } from '../register'
19
+ *
20
+ * assertSectionRenderParity(
21
+ * [{ id: '1', sectionType: 'pricing', content: { heading: 'Plans', plans: [] } }],
22
+ * { renderers: sectionRenderers },
23
+ * )
24
+ * ```
25
+ */
26
+ import { renderToStaticMarkup } from 'react-dom/server';
27
+ import { Sections } from './Sections.js';
28
+ /**
29
+ * Render options that mirror cms-admin `ADMIN_RENDER_OPTIONS`: identity
30
+ * sanitizer (rich text is sanitized on write) and a plain lazy `<img>` (no
31
+ * next/image pipeline inside the editor canvas).
32
+ */
33
+ export const CANVAS_RENDER_OPTIONS = {
34
+ sanitizeHtml: (html) => html,
35
+ baseUrl: '',
36
+ renderImage: ({ src, alt, className, priority, width, height }) => (
37
+ // eslint-disable-next-line @next/next/no-img-element
38
+ _jsx("img", { src: src, alt: alt, className: className, width: width ?? undefined, height: height ?? undefined, loading: priority ? undefined : 'lazy' })),
39
+ };
40
+ /**
41
+ * Normalize SSR HTML for stable equality across public vs canvas image
42
+ * pipelines. Strips whitespace and attributes that legitimately differ between
43
+ * `defaultRenderImage` and canvas plain `<img>` (loading / fetchpriority /
44
+ * decoding / sizes / LCP preload imageSizes) so structural drift still fails.
45
+ */
46
+ export function normalizeSectionHtml(html) {
47
+ return html
48
+ .replace(/\s+/g, ' ')
49
+ .replace(/\s(loading|fetchpriority|decoding|sizes|imagesizes|imagesrcset)="[^"]*"/gi, '')
50
+ .trim();
51
+ }
52
+ /** Public orchestrator path — Astro/Next site via `<Sections>`. */
53
+ export function renderPublicSectionsHtml(sections, opts) {
54
+ return renderToStaticMarkup(_jsx(Sections, { sections: sections, ctx: opts?.ctx, renderers: opts?.renderers, options: opts?.options }));
55
+ }
56
+ /**
57
+ * Canvas orchestrator path — same `<Sections>` tree with canvas-equivalent
58
+ * options (mirrors how custom kits receive `ADMIN_RENDER_OPTIONS`).
59
+ */
60
+ export function renderCanvasSectionsHtml(sections, opts) {
61
+ return renderToStaticMarkup(_jsx(Sections, { sections: sections, ctx: opts?.ctx, renderers: opts?.renderers, options: { ...CANVAS_RENDER_OPTIONS, ...opts?.options } }));
62
+ }
63
+ /**
64
+ * Assert that public and canvas orchestrators emit the same normalized tree
65
+ * for the given sections (+ optional shared renderer map).
66
+ *
67
+ * Throws with both HTML snippets on mismatch so CI failures are actionable.
68
+ */
69
+ export function assertSectionRenderParity(sections, opts) {
70
+ const normalize = opts?.normalize ?? normalizeSectionHtml;
71
+ const publicHtml = normalize(renderPublicSectionsHtml(sections, {
72
+ ctx: opts?.ctx,
73
+ renderers: opts?.renderers,
74
+ options: opts?.publicOptions,
75
+ }));
76
+ const canvasHtml = normalize(renderCanvasSectionsHtml(sections, {
77
+ ctx: opts?.ctx,
78
+ renderers: opts?.renderers,
79
+ options: opts?.canvasOptions,
80
+ }));
81
+ if (publicHtml !== canvasHtml) {
82
+ throw new Error([
83
+ 'Section render parity failed: public and canvas trees differ.',
84
+ '',
85
+ '--- public (normalized) ---',
86
+ publicHtml,
87
+ '',
88
+ '--- canvas (normalized) ---',
89
+ canvasHtml,
90
+ ].join('\n'));
91
+ }
92
+ }
93
+ /**
94
+ * Assert the three-layer section contract agrees: every declared `sectionType`
95
+ * has a renderer (or is an explicit data-only opt-out), and every renderer
96
+ * corresponds to a declared type — so renaming a schema id and leaving the old
97
+ * renderer behind fails instead of shipping Structure view placeholders.
98
+ *
99
+ * This is the check the docs call `verify:sections`. Wire it as a one-line
100
+ * test against the SAME map you pass to both `<Sections>` and
101
+ * `<AdminRoot sectionRenderers>`:
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { assertSectionCoverage } from '@prenta/sections-react/testing'
106
+ * import { marketingSectionTypes } from '../sections/types'
107
+ * import { sectionRenderers } from '../sections/register'
108
+ *
109
+ * assertSectionCoverage({
110
+ * declared: marketingSectionTypes,
111
+ * renderers: sectionRenderers,
112
+ * allowDataOnly: ['careers-listing'], // consumed by a code-owned template
113
+ * })
114
+ * ```
115
+ *
116
+ * @throws Error listing every problem at once so CI failures are actionable.
117
+ */
118
+ export function assertSectionCoverage(options) {
119
+ const declaredIds = options.declared.map((t) => (typeof t === 'string' ? t : t.id));
120
+ const declared = new Set(declaredIds);
121
+ const rendererIds = Object.keys(options.renderers);
122
+ const renderers = new Set(rendererIds);
123
+ const allowDataOnly = new Set(options.allowDataOnly ?? []);
124
+ const problems = [];
125
+ const seen = new Set();
126
+ for (const id of declaredIds) {
127
+ if (seen.has(id))
128
+ problems.push(`"${id}" is declared more than once in sections.types.`);
129
+ seen.add(id);
130
+ }
131
+ for (const id of declared) {
132
+ if (!renderers.has(id) && !allowDataOnly.has(id)) {
133
+ problems.push(`"${id}" is declared in sections.types but has no renderer — editors will see a ` +
134
+ `Structure view placeholder. Add it to the shared renderer map, or list it in ` +
135
+ `allowDataOnly if it is intentionally data-only.`);
136
+ }
137
+ }
138
+ for (const id of rendererIds) {
139
+ if (!declared.has(id)) {
140
+ problems.push(`the renderer map has "${id}" but sections.types does not declare it — a schema id ` +
141
+ `was renamed or removed while the old renderer stayed behind.`);
142
+ }
143
+ }
144
+ for (const id of allowDataOnly) {
145
+ if (!declared.has(id)) {
146
+ problems.push(`allowDataOnly lists "${id}" which is not declared in sections.types — remove it or fix the typo.`);
147
+ }
148
+ else if (renderers.has(id)) {
149
+ problems.push(`allowDataOnly lists "${id}" but the renderer map covers it — remove the stale opt-out.`);
150
+ }
151
+ }
152
+ if (problems.length > 0) {
153
+ throw new Error([
154
+ `Section coverage failed (${problems.length} problem${problems.length > 1 ? 's' : ''}):`,
155
+ ...problems.map((p) => ` - ${p}`),
156
+ ].join('\n'));
157
+ }
158
+ }
159
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Public types for the Prenta section renderer.
3
+ *
4
+ * A page/post's renderable content is the ordered `PageSection[]` stored on a
5
+ * document's `data.sections` JSON (the canonical content contract — see
6
+ * ADR 0001). This package turns that array into server-rendered React.
7
+ *
8
+ * The wire shape here is intentionally permissive (`content`/`settings` are
9
+ * open records) because it comes off the network as JSON. The authoritative
10
+ * field schemas live in `@prenta/core/page-sections`.
11
+ */
12
+ import type { ReactNode } from 'react';
13
+ import type { PublicForm } from '@prenta/core';
14
+ import type { LcpController } from './lcp.js';
15
+ /** A single section as delivered by the CMS (`data.sections[]`). */
16
+ export interface PublicSection {
17
+ id: string;
18
+ /** Keys into the section registry; unknown types render via the fallback. */
19
+ sectionType: string;
20
+ name?: string;
21
+ /** Hidden sections are skipped by `<Sections>`. Absent means visible. */
22
+ visible?: boolean;
23
+ content?: Record<string, unknown>;
24
+ settings?: Record<string, unknown>;
25
+ }
26
+ /**
27
+ * Per-request context for post-oriented sections (article body, author bio,
28
+ * related posts) and the post header. Supplied by the consumer's data layer;
29
+ * never trusted for HTML unless sanitized (see {@link RenderOptions}).
30
+ */
31
+ export interface PostContext {
32
+ title?: string;
33
+ excerpt?: string;
34
+ /** Post body HTML — used by the `article-body` section in `source: 'field'` mode. */
35
+ body?: string;
36
+ featuredImageUrl?: string;
37
+ /** Intrinsic featured-image dimensions (from `/resolve` enrichment) — avoids CLS. */
38
+ featuredImageWidth?: number | null;
39
+ featuredImageHeight?: number | null;
40
+ publishDate?: string | null;
41
+ category?: string;
42
+ author?: {
43
+ name?: string;
44
+ bio?: string;
45
+ avatarUrl?: string;
46
+ };
47
+ relatedPosts?: Array<{
48
+ title: string;
49
+ url?: string;
50
+ excerpt?: string;
51
+ }>;
52
+ }
53
+ /**
54
+ * Options threaded to every section renderer.
55
+ *
56
+ * `sanitizeHtml` is applied to author-supplied HTML (e.g. the `article-body`
57
+ * section) before it is injected via `dangerouslySetInnerHTML`.
58
+ *
59
+ * SECURITY: the default is the identity function because Prenta sanitizes
60
+ * rich text on **write** (cms-core `sanitizeHtml`, the allow-list source of
61
+ * truth). If you render HTML from an untrusted or third-party source, OR want
62
+ * defense-in-depth, pass a sanitizer here — e.g.
63
+ * `import { sanitizeHtml } from '@prenta/core'` in a server
64
+ * component, or any DOMPurify-style function.
65
+ */
66
+ /** Props passed to a {@link RenderImage} renderer. */
67
+ export interface RenderImageProps {
68
+ src: string;
69
+ alt: string;
70
+ /** Intrinsic dimensions from the media library, when known (helps avoid CLS). */
71
+ width?: number | null;
72
+ height?: number | null;
73
+ className?: string;
74
+ /** Above-the-fold images opt out of lazy-loading. */
75
+ priority?: boolean;
76
+ /** Responsive `sizes` hint, forwarded to image components that support it. */
77
+ sizes?: string;
78
+ }
79
+ /**
80
+ * Pluggable image renderer. The default emits a plain `<img>` with native
81
+ * lazy-loading; a Next.js consumer can pass a `next/image`-backed renderer to
82
+ * get responsive `srcset` + optimization without coupling this package to Next.
83
+ */
84
+ export type RenderImage = (props: RenderImageProps) => ReactNode;
85
+ export interface RenderOptions {
86
+ sanitizeHtml?: (html: string) => string;
87
+ /**
88
+ * Origin of the CMS API used by data-driven sections (e.g. the `form`
89
+ * section's `<PrentaForm>`). Defaults to `''` (same-origin relative URLs),
90
+ * correct for a Next.js app mounting the Prenta API at `/api/cms`.
91
+ */
92
+ baseUrl?: string;
93
+ /** Custom image renderer (e.g. `next/image`). Defaults to a lazy `<img>`. */
94
+ renderImage?: RenderImage;
95
+ /** Shared LCP (priority-image) budget. Pass ONE controller to both
96
+ * `PostHeader` and `Sections` so a post's featured image and its body
97
+ * sections compete for the same single priority slot. When omitted, each
98
+ * component creates its own (correct when only one is rendered).
99
+ */
100
+ lcp?: LcpController;
101
+ /**
102
+ * Pre-fetched public form schemas keyed by slug. When a `form` section
103
+ * references a slug present here, the server renders {@link PrentaFormHtml}
104
+ * (zero-JS + WebMCP) instead of the client {@link PrentaForm} island.
105
+ */
106
+ prefetchedForms?: Record<string, PublicForm>;
107
+ }
108
+ /** Internal: options with all defaults resolved. */
109
+ export interface ResolvedRenderOptions {
110
+ sanitizeHtml: (html: string) => string;
111
+ baseUrl: string;
112
+ renderImage: RenderImage;
113
+ lcp: LcpController;
114
+ prefetchedForms: Record<string, PublicForm>;
115
+ }
116
+ /** Props every section renderer receives. */
117
+ export interface SectionRendererProps {
118
+ section: PublicSection;
119
+ ctx?: PostContext;
120
+ options: ResolvedRenderOptions;
121
+ }
122
+ /**
123
+ * A section renderer: maps one section's content to React. Renderers must be
124
+ * SSR-safe (no browser-only APIs at module/render time) so they work in React
125
+ * Server Components.
126
+ */
127
+ export type SectionRenderer = (props: SectionRendererProps) => ReactNode;
128
+ /** A map of section-type id → renderer (used for overrides + the defaults). */
129
+ export type SectionRendererMap = Record<string, SectionRenderer>;
130
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE7C,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5D,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACxE;AAED;;;;;;;;;;;;GAYG;AACH,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,SAAS,CAAA;AAEhE,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;OAIG;IACH,GAAG,CAAC,EAAE,aAAa,CAAA;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAC7C;AAED,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,EAAE,aAAa,CAAA;IAClB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAC5C;AAED,6CAA6C;AAC7C,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAA;IACtB,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,OAAO,EAAE,qBAAqB,CAAA;CAC/B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,oBAAoB,KAAK,SAAS,CAAA;AAExE,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Small, pure helpers shared by the default section renderers. No DOM or
3
+ * browser APIs — these run in React Server Components.
4
+ */
5
+ /** Read a string field from a section's content, with a fallback. */
6
+ export declare function str(content: Record<string, unknown> | undefined, key: string, fallback?: string): string;
7
+ /** Read a boolean field from a section's content, with a fallback. */
8
+ export declare function bool(content: Record<string, unknown> | undefined, key: string, fallback?: boolean): boolean;
9
+ /** Read a finite number field from a section's content, else `null`. */
10
+ export declare function num(content: Record<string, unknown> | undefined, key: string): number | null;
11
+ /** Read an array-of-records field (e.g. `stats`, `images`) defensively. */
12
+ export declare function rows(content: Record<string, unknown> | undefined, key: string): Array<Record<string, unknown>>;
13
+ /** Vertical padding utility class from a section's `paddingY` setting. */
14
+ export declare function paddingClass(settings: Record<string, unknown> | undefined): string;
15
+ /** Background + text-color utility classes from a section's background settings. */
16
+ export declare function backgroundClass(settings: Record<string, unknown> | undefined): string;
17
+ /** True when a section uses a dark (purple) background — drives button tone. */
18
+ export declare function isDark(settings: Record<string, unknown> | undefined): boolean;
19
+ /** Format an ISO date for a post header; empty string when missing/invalid. */
20
+ export declare function formatHeaderDate(value?: string | null): string;
21
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qEAAqE;AACrE,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,GAAG,EAAE,MAAM,EACX,QAAQ,SAAK,GACZ,MAAM,CAGR;AAED,sEAAsE;AACtE,wBAAgB,IAAI,CAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,GAAG,EAAE,MAAM,EACX,QAAQ,UAAQ,GACf,OAAO,CAGT;AAED,wEAAwE;AACxE,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5F;AAED,2EAA2E;AAC3E,wBAAgB,IAAI,CAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,GAAG,EAAE,MAAM,GACV,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAGhC;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,CAWlF;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,CASrF;AAED,gFAAgF;AAChF,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,OAAO,CAK7E;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK9D"}
package/dist/utils.js ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Small, pure helpers shared by the default section renderers. No DOM or
3
+ * browser APIs — these run in React Server Components.
4
+ */
5
+ /** Read a string field from a section's content, with a fallback. */
6
+ export function str(content, key, fallback = '') {
7
+ const v = content?.[key];
8
+ return typeof v === 'string' ? v : fallback;
9
+ }
10
+ /** Read a boolean field from a section's content, with a fallback. */
11
+ export function bool(content, key, fallback = false) {
12
+ const v = content?.[key];
13
+ return typeof v === 'boolean' ? v : fallback;
14
+ }
15
+ /** Read a finite number field from a section's content, else `null`. */
16
+ export function num(content, key) {
17
+ const v = content?.[key];
18
+ return typeof v === 'number' && Number.isFinite(v) ? v : null;
19
+ }
20
+ /** Read an array-of-records field (e.g. `stats`, `images`) defensively. */
21
+ export function rows(content, key) {
22
+ const v = content?.[key];
23
+ return Array.isArray(v) ? v : [];
24
+ }
25
+ /** Vertical padding utility class from a section's `paddingY` setting. */
26
+ export function paddingClass(settings) {
27
+ switch (settings?.paddingY) {
28
+ case 'sm':
29
+ return 'py-8';
30
+ case 'md':
31
+ return 'py-12';
32
+ case 'xl':
33
+ return 'py-20 sm:py-28';
34
+ default:
35
+ return 'py-16 sm:py-20';
36
+ }
37
+ }
38
+ /** Background + text-color utility classes from a section's background settings. */
39
+ export function backgroundClass(settings) {
40
+ const type = settings?.backgroundType;
41
+ const color = settings?.backgroundColor;
42
+ if (type === 'gradient' && color === 'purple') {
43
+ return 'bg-gradient-to-br from-violet-600 to-violet-700 text-white';
44
+ }
45
+ if (type === 'solid' && color === 'purple')
46
+ return 'bg-violet-600 text-white';
47
+ if (type === 'solid' && color === 'muted')
48
+ return 'bg-zinc-50 text-zinc-900';
49
+ return 'bg-white text-zinc-900';
50
+ }
51
+ /** True when a section uses a dark (purple) background — drives button tone. */
52
+ export function isDark(settings) {
53
+ return ((settings?.backgroundType === 'solid' || settings?.backgroundType === 'gradient') &&
54
+ settings?.backgroundColor === 'purple');
55
+ }
56
+ /** Format an ISO date for a post header; empty string when missing/invalid. */
57
+ export function formatHeaderDate(value) {
58
+ if (!value)
59
+ return '';
60
+ const d = new Date(value);
61
+ if (Number.isNaN(d.getTime()))
62
+ return '';
63
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
64
+ }
65
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { PublicForm, PublicFormField } from './forms.js';
2
+ /** Lowercase WebMCP declarative attributes for a `<form>` element. */
3
+ export declare function webMcpFormAttributes(form: PublicForm): Record<string, string | boolean>;
4
+ /** `toolparamdescription` for a single field when WebMCP is enabled. */
5
+ export declare function webMcpFieldAttribute(form: PublicForm, field: PublicFormField): Record<string, string>;
6
+ //# sourceMappingURL=webmcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.d.ts","sourceRoot":"","sources":["../src/webmcp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE7D,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAQvF;AAED,wEAAwE;AACxE,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,eAAe,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGxB"}
package/dist/webmcp.js ADDED
@@ -0,0 +1,18 @@
1
+ /** Lowercase WebMCP declarative attributes for a `<form>` element. */
2
+ export function webMcpFormAttributes(form) {
3
+ if (!form.webmcp?.enabled)
4
+ return {};
5
+ const attrs = {
6
+ toolname: form.webmcp.toolName,
7
+ tooldescription: form.webmcp.toolDescription,
8
+ };
9
+ if (form.webmcp.autoSubmit)
10
+ attrs.toolautosubmit = true;
11
+ return attrs;
12
+ }
13
+ /** `toolparamdescription` for a single field when WebMCP is enabled. */
14
+ export function webMcpFieldAttribute(form, field) {
15
+ const desc = form.webmcp?.fieldParams[field.key];
16
+ return desc ? { toolparamdescription: desc } : {};
17
+ }
18
+ //# sourceMappingURL=webmcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.js","sourceRoot":"","sources":["../src/webmcp.ts"],"names":[],"mappings":"AAEA,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,IAAgB;IACnD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,KAAK,GAAqC;QAC9C,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;QAC9B,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe;KAC7C,CAAA;IACD,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,KAAK,CAAC,cAAc,GAAG,IAAI,CAAA;IACvD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAClC,IAAgB,EAChB,KAAsB;IAEtB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChD,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACnD,CAAC"}