@plenary/core 0.1.1
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/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/index.cjs +159 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +413 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.js +122 -0
- package/dist/index.js.map +1 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick DeLoughry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @plenary/core
|
|
2
|
+
|
|
3
|
+
The shared type layer of the Plenary SDK: the JSON DOM a page builder document
|
|
4
|
+
is made of, the component/prop/slot registration contract, the style panel and
|
|
5
|
+
theme types, and the postMessage protocol that connects a site's live preview
|
|
6
|
+
to the Plenary console.
|
|
7
|
+
|
|
8
|
+
Types only, zero runtime dependencies. You normally don't install this
|
|
9
|
+
directly — it comes in as a dependency of `@plenary/client` and
|
|
10
|
+
`@plenary/react` — but it's the package to import from when you need the
|
|
11
|
+
underlying types:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import type { PlenaryDocument, PlenaryNode } from "@plenary/core";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **`PlenaryDocument` / `PlenaryNode`** — the component tree stored in an
|
|
18
|
+
entry's `blocks` field: component name, props, styles per breakpoint, and
|
|
19
|
+
named slots of child nodes.
|
|
20
|
+
- **Registration types** — `ComponentRegistration`, `PropDefinition`,
|
|
21
|
+
`SlotDefinition`, `StylePanel`, `ThemeRegistration`: what a framework
|
|
22
|
+
adapter reports to the console so the page builder knows which components
|
|
23
|
+
exist and how to edit them.
|
|
24
|
+
- **Preview protocol** — the message types exchanged between the console's
|
|
25
|
+
page builder and an embedded site preview. Origin discipline is the security
|
|
26
|
+
model: `isAllowedEditorOrigin` matches an origin against a configured list
|
|
27
|
+
(exact origins, `https://*.example.com` subdomain patterns, or
|
|
28
|
+
`http://localhost:*` port patterns) and everything else is ignored.
|
|
29
|
+
|
|
30
|
+
Framework adapters build on these types; `@plenary/react` is the reference
|
|
31
|
+
implementation. Full documentation at
|
|
32
|
+
[docs.plenarycms.com](https://docs.plenarycms.com).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BREAKPOINTS: () => BREAKPOINTS,
|
|
24
|
+
DEFAULT_CUTOFFS: () => DEFAULT_CUTOFFS,
|
|
25
|
+
DND_NEW: () => DND_NEW,
|
|
26
|
+
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
27
|
+
STYLE_PROP_FOR_BREAKPOINT: () => STYLE_PROP_FOR_BREAKPOINT,
|
|
28
|
+
compileNodeCss: () => compileNodeCss,
|
|
29
|
+
effectiveStyle: () => effectiveStyle,
|
|
30
|
+
hasWildcardPattern: () => hasWildcardPattern,
|
|
31
|
+
isAllowedEditorOrigin: () => isAllowedEditorOrigin,
|
|
32
|
+
readNodeStyles: () => readNodeStyles,
|
|
33
|
+
resolveCutoffs: () => resolveCutoffs
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/origins.ts
|
|
38
|
+
function parsePattern(pattern) {
|
|
39
|
+
const match = pattern.trim().toLowerCase().match(/^([a-z][a-z0-9+.-]*):\/\/([^/:*]+|\*\.[^/:*]+)(?::(\d+|\*))?$/);
|
|
40
|
+
if (!match) return null;
|
|
41
|
+
const [, scheme, rawHost, port] = match;
|
|
42
|
+
const anySubdomain = rawHost.startsWith("*.");
|
|
43
|
+
return {
|
|
44
|
+
scheme,
|
|
45
|
+
host: anySubdomain ? rawHost.slice(2) : rawHost,
|
|
46
|
+
anySubdomain,
|
|
47
|
+
port: port ?? ""
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function isAllowedEditorOrigin(patterns, origin) {
|
|
51
|
+
let url;
|
|
52
|
+
try {
|
|
53
|
+
url = new URL(origin);
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const scheme = url.protocol.replace(/:$/, "");
|
|
58
|
+
const host = url.hostname.toLowerCase();
|
|
59
|
+
const port = url.port;
|
|
60
|
+
return patterns.some((pattern) => {
|
|
61
|
+
const p = parsePattern(pattern);
|
|
62
|
+
if (!p || p.scheme !== scheme) return false;
|
|
63
|
+
if (p.port !== "*" && p.port !== port) return false;
|
|
64
|
+
if (p.anySubdomain) return host !== p.host && host.endsWith("." + p.host);
|
|
65
|
+
return p.host === host;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function hasWildcardPattern(patterns) {
|
|
69
|
+
return patterns.some((p) => p.includes("*"));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/breakpoints.ts
|
|
73
|
+
var BREAKPOINTS = ["base", "tablet", "mobile"];
|
|
74
|
+
var STYLE_PROP_FOR_BREAKPOINT = {
|
|
75
|
+
base: "style",
|
|
76
|
+
tablet: "styleTablet",
|
|
77
|
+
mobile: "styleMobile"
|
|
78
|
+
};
|
|
79
|
+
var DEFAULT_CUTOFFS = { tablet: 1024, mobile: 640 };
|
|
80
|
+
function styleLayer(value) {
|
|
81
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
function readNodeStyles(props) {
|
|
85
|
+
return {
|
|
86
|
+
base: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.base]),
|
|
87
|
+
tablet: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.tablet]),
|
|
88
|
+
mobile: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.mobile])
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function effectiveStyle(styles, breakpoint) {
|
|
92
|
+
const out = { ...styles.base ?? {} };
|
|
93
|
+
if (breakpoint !== "base") Object.assign(out, styles.tablet ?? {});
|
|
94
|
+
if (breakpoint === "mobile") Object.assign(out, styles.mobile ?? {});
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function resolveCutoffs(theme) {
|
|
98
|
+
const declared = theme?.breakpoints;
|
|
99
|
+
const pick = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
100
|
+
return {
|
|
101
|
+
tablet: pick(declared?.tablet, DEFAULT_CUTOFFS.tablet),
|
|
102
|
+
mobile: pick(declared?.mobile, DEFAULT_CUTOFFS.mobile)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function toCssName(key) {
|
|
106
|
+
if (key.startsWith("--")) return key;
|
|
107
|
+
return key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
108
|
+
}
|
|
109
|
+
var PROPERTY_NAME = /^(--[\w-]+|[a-z][a-z-]*)$/;
|
|
110
|
+
var FORBIDDEN_IN_VALUE = /[{}\\\u0000-\u001f\u007f]|\/\*|<\/style/i;
|
|
111
|
+
function sanitizeValue(raw) {
|
|
112
|
+
const value = raw.trim();
|
|
113
|
+
return value && !FORBIDDEN_IN_VALUE.test(value) ? value : null;
|
|
114
|
+
}
|
|
115
|
+
function declarations(layer) {
|
|
116
|
+
const out = [];
|
|
117
|
+
for (const [key, raw] of Object.entries(layer)) {
|
|
118
|
+
if (typeof raw !== "string") continue;
|
|
119
|
+
const property = toCssName(key.trim());
|
|
120
|
+
if (!PROPERTY_NAME.test(property)) continue;
|
|
121
|
+
const value = sanitizeValue(raw);
|
|
122
|
+
if (value === null) continue;
|
|
123
|
+
out.push(`${property}: ${value} !important;`);
|
|
124
|
+
}
|
|
125
|
+
return out.join(" ");
|
|
126
|
+
}
|
|
127
|
+
function escapeAttr(id) {
|
|
128
|
+
return id.replace(/[\u0000-\u001f\u007f]/g, "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
129
|
+
}
|
|
130
|
+
function compileNodeCss(id, styles, cutoffs = DEFAULT_CUTOFFS) {
|
|
131
|
+
const selector = `[data-plenary-id="${escapeAttr(id)}"] > *`;
|
|
132
|
+
const parts = [];
|
|
133
|
+
const base = styles.base ? declarations(styles.base) : "";
|
|
134
|
+
if (base) parts.push(`${selector} { ${base} }`);
|
|
135
|
+
const tablet = styles.tablet ? declarations(styles.tablet) : "";
|
|
136
|
+
if (tablet) parts.push(`@media (max-width: ${cutoffs.tablet}px) { ${selector} { ${tablet} } }`);
|
|
137
|
+
const mobile = styles.mobile ? declarations(styles.mobile) : "";
|
|
138
|
+
if (mobile) parts.push(`@media (max-width: ${cutoffs.mobile}px) { ${selector} { ${mobile} } }`);
|
|
139
|
+
return parts.join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/protocol.ts
|
|
143
|
+
var PROTOCOL_VERSION = 1;
|
|
144
|
+
var DND_NEW = "application/x-plenary-new";
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
BREAKPOINTS,
|
|
148
|
+
DEFAULT_CUTOFFS,
|
|
149
|
+
DND_NEW,
|
|
150
|
+
PROTOCOL_VERSION,
|
|
151
|
+
STYLE_PROP_FOR_BREAKPOINT,
|
|
152
|
+
compileNodeCss,
|
|
153
|
+
effectiveStyle,
|
|
154
|
+
hasWildcardPattern,
|
|
155
|
+
isAllowedEditorOrigin,
|
|
156
|
+
readNodeStyles,
|
|
157
|
+
resolveCutoffs
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/origins.ts","../src/breakpoints.ts","../src/protocol.ts"],"sourcesContent":["/**\n * @plenary/core — the shared contract for the Plenary SDK: the DOM node model,\n * component registration, prop definitions, the preview protocol, and the\n * framework-agnostic runtime pieces every adapter needs (editor-origin\n * matching, the responsive style model and its CSS compiler). Zero deps.\n */\n\nexport type { PlenaryNode, PlenaryDocument } from \"./dom.js\";\nexport { isAllowedEditorOrigin, hasWildcardPattern } from \"./origins.js\";\nexport {\n BREAKPOINTS,\n STYLE_PROP_FOR_BREAKPOINT,\n DEFAULT_CUTOFFS,\n readNodeStyles,\n effectiveStyle,\n resolveCutoffs,\n compileNodeCss,\n type Breakpoint,\n type BreakpointCutoffs,\n type NodeStyles,\n} from \"./breakpoints.js\";\nexport type {\n PropFieldType,\n PropDefinition,\n SlotDefinition,\n StylePanel,\n ComponentRegistration,\n ThemeToken,\n ThemeRegistration,\n} from \"./registration.js\";\nexport {\n PROTOCOL_VERSION,\n DND_NEW,\n type PlenaryMessage,\n type ManifestComponent,\n type HelloPayload,\n type InitPayload,\n type ManifestPayload,\n type RenderPayload,\n type PatchPayload,\n type SelectPayload,\n type HoverPayload,\n type HighlightPayload,\n type CanvasDropTarget,\n type CanvasDropPayload,\n type SizePayload,\n type WheelPayload,\n type PanPayload,\n type KeyPayload,\n type EditPayload,\n type HelloMessage,\n type InitMessage,\n type ManifestMessage,\n type RenderMessage,\n type PatchMessage,\n type SelectMessage,\n type HoverMessage,\n type HighlightMessage,\n type CanvasDropMessage,\n type SizeMessage,\n type WheelMessage,\n type PanMessage,\n type KeyMessage,\n type EditMessage,\n type IframeToConsoleMessage,\n type ConsoleToIframeMessage,\n type PlenaryProtocolMessage,\n type PlenaryMessagePayloads,\n type PlenaryMessageType,\n} from \"./protocol.js\";\n","/**\n * Editor-origin matching — which console origins a site's preview bridge will\n * speak to. A site lists patterns; every inbound bridge message must come from\n * an origin matching one of them.\n *\n * Pattern forms:\n *\n * - Exact origin: `https://cms.example.com`, `http://localhost:5173`\n * - Subdomain wildcard: `https://*.example.com` — matches any depth of\n * subdomain (`a.example.com`, `a.b.example.com`) but never the bare apex.\n * `*.` is only valid as the leftmost host label.\n * - Port wildcard: `http://localhost:*` — matches any port, including none.\n *\n * The scheme is always literal (no wildcard), and a bare `*` host is not\n * supported: allowing any origin would let any page that frames the site drive\n * its preview. List the origins you trust, wildcarded no wider than a domain\n * you control.\n */\n\n/** One parsed pattern; null when the pattern is malformed (matches nothing). */\ninterface ParsedPattern {\n scheme: string;\n /** Host to compare against; when `anySubdomain`, the apex the host must be under. */\n host: string;\n anySubdomain: boolean;\n /** Explicit port, \"\" for scheme-default, or \"*\" for any. */\n port: string;\n}\n\nfunction parsePattern(pattern: string): ParsedPattern | null {\n const match = pattern\n .trim()\n .toLowerCase()\n .match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/:*]+|\\*\\.[^/:*]+)(?::(\\d+|\\*))?$/);\n if (!match) return null;\n const [, scheme, rawHost, port] = match;\n const anySubdomain = rawHost!.startsWith(\"*.\");\n return {\n scheme: scheme!,\n host: anySubdomain ? rawHost!.slice(2) : rawHost!,\n anySubdomain,\n port: port ?? \"\",\n };\n}\n\n/**\n * True when `origin` (a browser origin string, e.g. a MessageEvent's `origin`)\n * is allowed by any of `patterns`. Malformed patterns and origins match nothing.\n *\n * Browsers normalize origins to omit scheme-default ports, so a pattern without\n * a port matches exactly the default-port origin, and an explicit port must\n * match explicitly.\n */\nexport function isAllowedEditorOrigin(patterns: readonly string[], origin: string): boolean {\n let url: URL;\n try {\n url = new URL(origin);\n } catch {\n return false;\n }\n const scheme = url.protocol.replace(/:$/, \"\");\n const host = url.hostname.toLowerCase();\n const port = url.port;\n\n return patterns.some((pattern) => {\n const p = parsePattern(pattern);\n if (!p || p.scheme !== scheme) return false;\n if (p.port !== \"*\" && p.port !== port) return false;\n if (p.anySubdomain) return host !== p.host && host.endsWith(\".\" + p.host);\n return p.host === host;\n });\n}\n\n/** True when any pattern uses a wildcard (and so cannot be a postMessage target). */\nexport function hasWildcardPattern(patterns: readonly string[]): boolean {\n return patterns.some((p) => p.includes(\"*\"));\n}\n","/**\n * The responsive style model: three style layers per node, desktop-first.\n *\n * A node's editor styles live on its props as flat camelCase records —\n * `style` (base), `styleTablet`, `styleMobile`. `tablet` overrides `base` at\n * widths ≤ the tablet cutoff, `mobile` overrides both at ≤ the mobile cutoff;\n * overrides are per-property, not per-object.\n *\n * The compiler turns those layers into per-node CSS targeting the renderer's\n * annotation wrapper (`[data-plenary-id=\"…\"] > *`). Every declaration is\n * emitted `!important`: editor styles were historically inline (which beats\n * all non-important site CSS), and the generated rules preserve exactly that\n * precedence while still cascading correctly among themselves — the three\n * rules share one specificity, so the later media block wins where it applies.\n */\n\nimport type { ThemeRegistration } from \"./registration.js\";\n\nexport type Breakpoint = \"base\" | \"tablet\" | \"mobile\";\n\nexport const BREAKPOINTS: readonly Breakpoint[] = [\"base\", \"tablet\", \"mobile\"];\n\n/** The node prop each breakpoint's style layer is stored in. */\nexport const STYLE_PROP_FOR_BREAKPOINT: Record<Breakpoint, string> = {\n base: \"style\",\n tablet: \"styleTablet\",\n mobile: \"styleMobile\",\n};\n\n/** The `max-width` cutoffs (px) at which the override layers apply. */\nexport interface BreakpointCutoffs {\n tablet: number;\n mobile: number;\n}\n\nexport const DEFAULT_CUTOFFS: BreakpointCutoffs = { tablet: 1024, mobile: 640 };\n\n/** A node's three style layers; absent layer = no styles there. */\nexport interface NodeStyles {\n base?: Record<string, string>;\n tablet?: Record<string, string>;\n mobile?: Record<string, string>;\n}\n\n/** One flat camelCase style record, defensively narrowed from a prop value. */\nfunction styleLayer(value: unknown): Record<string, string> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n return value as Record<string, string>;\n}\n\n/** Read a node's style layers off its props. */\nexport function readNodeStyles(props: Record<string, unknown>): NodeStyles {\n return {\n base: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.base]),\n tablet: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.tablet]),\n mobile: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.mobile]),\n };\n}\n\n/**\n * The effective (cascaded) style at a breakpoint: base, overlaid per-property\n * by tablet (at tablet and below), then mobile (at mobile only).\n */\nexport function effectiveStyle(\n styles: NodeStyles,\n breakpoint: Breakpoint,\n): Record<string, string> {\n const out: Record<string, string> = { ...(styles.base ?? {}) };\n if (breakpoint !== \"base\") Object.assign(out, styles.tablet ?? {});\n if (breakpoint === \"mobile\") Object.assign(out, styles.mobile ?? {});\n return out;\n}\n\n/**\n * The cutoffs a site's theme declares, defaulted and validated — a cutoff must\n * be a positive finite number of px or it falls back to the default.\n */\nexport function resolveCutoffs(theme: ThemeRegistration | undefined): BreakpointCutoffs {\n const declared = theme?.breakpoints;\n const pick = (value: number | undefined, fallback: number) =>\n typeof value === \"number\" && Number.isFinite(value) && value > 0\n ? Math.floor(value)\n : fallback;\n return {\n tablet: pick(declared?.tablet, DEFAULT_CUTOFFS.tablet),\n mobile: pick(declared?.mobile, DEFAULT_CUTOFFS.mobile),\n };\n}\n\n// --- compilation -----------------------------------------------------------\n\n/** camelCase style key → kebab-case CSS property. Custom properties pass through. */\nfunction toCssName(key: string): string {\n if (key.startsWith(\"--\")) return key;\n return key.replace(/[A-Z]/g, (m) => \"-\" + m.toLowerCase());\n}\n\n/** A legal property name after kebab conversion: `color`, `font-size`, `--x`. */\nconst PROPERTY_NAME = /^(--[\\w-]+|[a-z][a-z-]*)$/;\n\n/**\n * Sequences a value may never contain: rule/selector escapes (braces,\n * backslash), comment openers, a premature `</style`, and control characters.\n * A value carrying any of them is dropped whole — no repair attempts, since a\n * \"repaired\" value is a value the user never wrote. `;` is allowed: it can at\n * worst add a declaration to the node's own rule, which the CSS editor permits\n * anyway, and data URIs need it.\n */\nconst FORBIDDEN_IN_VALUE = /[{}\\\\\\u0000-\\u001f\\u007f]|\\/\\*|<\\/style/i;\n\nfunction sanitizeValue(raw: string): string | null {\n const value = raw.trim();\n return value && !FORBIDDEN_IN_VALUE.test(value) ? value : null;\n}\n\n/** One layer's declarations as CSS text; \"\" when nothing survives. */\nfunction declarations(layer: Record<string, string>): string {\n const out: string[] = [];\n for (const [key, raw] of Object.entries(layer)) {\n if (typeof raw !== \"string\") continue;\n const property = toCssName(key.trim());\n if (!PROPERTY_NAME.test(property)) continue;\n const value = sanitizeValue(raw);\n if (value === null) continue;\n out.push(`${property}: ${value} !important;`);\n }\n return out.join(\" \");\n}\n\n/** The node id, escaped for use inside a double-quoted attribute selector. */\nfunction escapeAttr(id: string): string {\n return id.replace(/[\\u0000-\\u001f\\u007f]/g, \"\").replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Compile one node's style layers to CSS: a base rule plus a `@media\n * (max-width)` block per override layer, in cascade order. Returns \"\" when no\n * declaration survives — callers skip the node entirely.\n */\nexport function compileNodeCss(\n id: string,\n styles: NodeStyles,\n cutoffs: BreakpointCutoffs = DEFAULT_CUTOFFS,\n): string {\n const selector = `[data-plenary-id=\"${escapeAttr(id)}\"] > *`;\n const parts: string[] = [];\n const base = styles.base ? declarations(styles.base) : \"\";\n if (base) parts.push(`${selector} { ${base} }`);\n const tablet = styles.tablet ? declarations(styles.tablet) : \"\";\n if (tablet) parts.push(`@media (max-width: ${cutoffs.tablet}px) { ${selector} { ${tablet} } }`);\n const mobile = styles.mobile ? declarations(styles.mobile) : \"\";\n if (mobile) parts.push(`@media (max-width: ${cutoffs.mobile}px) { ${selector} { ${mobile} } }`);\n return parts.join(\"\\n\");\n}\n","/**\n * The postMessage protocol — the contract between a site's preview bridge\n * (`@plenary/react`) and the console's page builder. These types are the single\n * source of truth every framework adapter and the console share.\n *\n * Origin discipline is the security model: the bridge ignores any message whose\n * origin is not in its configured `editorOrigins`, and the console ignores any\n * message not from the iframe's known origin.\n */\n\nimport type {\n PropDefinition,\n SlotDefinition,\n StylePanel,\n ThemeRegistration,\n} from \"./registration.js\";\n\n/** Current protocol version; negotiated in the handshake. Bumped on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Drag data-transfer key for a new component dragged from the console palette.\n * The bridge reads it on `drop` over the live preview to know which component to\n * insert. Kept in sync with the console's own constant.\n */\nexport const DND_NEW = \"application/x-plenary-new\";\n\n/** Every message on the wire is wrapped in this envelope. */\nexport interface PlenaryMessage<TType extends string, TPayload> {\n /** Discriminates Plenary traffic from other postMessage senders. */\n plenary: true;\n /** Protocol version of the sender. */\n v: number;\n type: TType;\n payload: TPayload;\n}\n\n/** A component as announced in the manifest (normalized from its registration). */\nexport interface ManifestComponent {\n /** Registration key used in the DOM (\"hero\"). */\n name: string;\n label: string;\n icon?: string;\n props: PropDefinition[];\n slots: SlotDefinition[];\n category?: string;\n /** Style panels the component exposes; absent means none. */\n styles?: StylePanel[];\n}\n\n// --- Payloads -------------------------------------------------------------\n\n/** `hello` — bridge announces it is ready. */\nexport interface HelloPayload {\n protocolVersion: number;\n sdkVersion: string;\n}\n\n/** `init` — console acks the handshake and supplies context. */\nexport interface InitPayload {\n entryId: string;\n modelKey: string;\n editorOrigin: string;\n}\n\n/** `manifest` — the full component catalog (built-ins + custom) and theme. */\nexport interface ManifestPayload {\n components: ManifestComponent[];\n /** The site's design tokens, when it registered any. */\n theme?: ThemeRegistration;\n}\n\n/**\n * `render` — the draft entry the bridge should render instead of the API's copy.\n * A component tree lives in `data` as a `blocks` field; the bridge renders the\n * field named by its `region`.\n */\nexport interface RenderPayload {\n data: Record<string, unknown>;\n locale: string;\n}\n\n/** `patch` — a targeted prop update (optimization; v1 may just re-send `render`). */\nexport interface PatchPayload {\n nodeId: string;\n props: Record<string, unknown>;\n}\n\n/** `select` — user clicked a node in the preview. */\nexport interface SelectPayload {\n nodeId: string;\n}\n\n/** `hover` — pointer moved over a node (or off all nodes). */\nexport interface HoverPayload {\n nodeId: string | null;\n}\n\n/**\n * `highlight` — console asks the preview to outline nodes from the sidebar.\n * Two independent channels, drawn with distinct styles: `selectedId` is the\n * block being edited (strong outline), `hoverId` is whatever the cursor is over\n * — a tree row or a preview element — (lighter outline). Either may be null.\n */\nexport interface HighlightPayload {\n selectedId: string | null;\n hoverId: string | null;\n}\n\n/** Where a component dropped on the live canvas should land, in tree terms. */\nexport type CanvasDropTarget =\n | { at: \"root\" }\n | { at: \"before\" | \"after\"; nodeId: string }\n | { at: \"slot\"; nodeId: string; slot: string };\n\n/** `canvasDrop` — a palette component was dropped onto the rendered preview. */\nexport interface CanvasDropPayload {\n component: string;\n target: CanvasDropTarget;\n}\n\n/**\n * `size` — the page's full content height in CSS px, reported by the bridge on\n * load and whenever it changes. The console's artboard sizes the preview iframe\n * to it so the whole page is visible with no inner scrollbar.\n */\nexport interface SizePayload {\n height: number;\n}\n\n/**\n * `wheel` — a Ctrl/Cmd+wheel zoom gesture performed over the page. The iframe\n * consumes wheel events, so the bridge forwards them for the console's artboard\n * to zoom; `x`/`y` are the cursor in page (iframe-viewport) coordinates, which\n * are the artboard's unscaled frame coordinates.\n */\nexport interface WheelPayload {\n deltaY: number;\n x: number;\n y: number;\n}\n\n/**\n * `pan` — a middle-button drag step over the page (the hand tool). `dx`/`dy`\n * are the pointer's movement since the last step, in page coordinates.\n */\nexport interface PanPayload {\n dx: number;\n dy: number;\n}\n\n/**\n * `key` — an editor keyboard shortcut pressed while focus is inside the frame\n * (clicking a block focuses the iframe, so the console's own listener never\n * sees these). The bridge forwards only recognized shortcut combinations and\n * prevents their default in the frame; the console dispatches them exactly as\n * if they had been pressed on its window.\n */\nexport interface KeyPayload {\n key: string;\n ctrlKey: boolean;\n metaKey: boolean;\n shiftKey: boolean;\n}\n\n/**\n * `edit` — an in-place edit committed inside the frame (double-click inline\n * text editing). The bridge has already applied it to its local draft; the\n * console applies it to the real draft as one undoable step and its next\n * `render` echo matches what the frame is showing.\n */\nexport interface EditPayload {\n nodeId: string;\n props: Record<string, unknown>;\n}\n\n// --- Messages -------------------------------------------------------------\n\nexport type HelloMessage = PlenaryMessage<\"hello\", HelloPayload>;\nexport type InitMessage = PlenaryMessage<\"init\", InitPayload>;\nexport type ManifestMessage = PlenaryMessage<\"manifest\", ManifestPayload>;\nexport type RenderMessage = PlenaryMessage<\"render\", RenderPayload>;\nexport type PatchMessage = PlenaryMessage<\"patch\", PatchPayload>;\nexport type SelectMessage = PlenaryMessage<\"select\", SelectPayload>;\nexport type HoverMessage = PlenaryMessage<\"hover\", HoverPayload>;\nexport type HighlightMessage = PlenaryMessage<\"highlight\", HighlightPayload>;\nexport type CanvasDropMessage = PlenaryMessage<\"canvasDrop\", CanvasDropPayload>;\nexport type SizeMessage = PlenaryMessage<\"size\", SizePayload>;\nexport type WheelMessage = PlenaryMessage<\"wheel\", WheelPayload>;\nexport type PanMessage = PlenaryMessage<\"pan\", PanPayload>;\nexport type KeyMessage = PlenaryMessage<\"key\", KeyPayload>;\nexport type EditMessage = PlenaryMessage<\"edit\", EditPayload>;\n\n/** Messages the bridge (iframe) sends up to the console. */\nexport type IframeToConsoleMessage =\n | HelloMessage\n | ManifestMessage\n | SelectMessage\n | HoverMessage\n | CanvasDropMessage\n | SizeMessage\n | WheelMessage\n | PanMessage\n | KeyMessage\n | EditMessage;\n\n/** Messages the console sends down to the bridge (iframe). */\nexport type ConsoleToIframeMessage =\n | InitMessage\n | RenderMessage\n | PatchMessage\n | HighlightMessage;\n\n/** Any message in the protocol, either direction. */\nexport type PlenaryProtocolMessage = IframeToConsoleMessage | ConsoleToIframeMessage;\n\n/** Maps each message `type` to its payload — useful for typed senders/handlers. */\nexport interface PlenaryMessagePayloads {\n hello: HelloPayload;\n init: InitPayload;\n manifest: ManifestPayload;\n render: RenderPayload;\n patch: PatchPayload;\n select: SelectPayload;\n hover: HoverPayload;\n highlight: HighlightPayload;\n canvasDrop: CanvasDropPayload;\n size: SizePayload;\n wheel: WheelPayload;\n pan: PanPayload;\n key: KeyPayload;\n edit: EditPayload;\n}\n\n/** Every protocol message `type` string. */\nexport type PlenaryMessageType = keyof PlenaryMessagePayloads;\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DOM node model — the serialized component tree stored in an entry's `dom`
|
|
3
|
+
* field and rendered by a framework adapter (`@plenary/react`).
|
|
4
|
+
*
|
|
5
|
+
* Built-in and custom components are indistinguishable in the tree: every node
|
|
6
|
+
* references a registered component by name. That is the whole point of
|
|
7
|
+
* pre-registering the primitives.
|
|
8
|
+
*/
|
|
9
|
+
/** A single node in a Page's component tree. */
|
|
10
|
+
interface PlenaryNode {
|
|
11
|
+
/** Stable id minted by the console; survives reorders. The SDK never generates ids. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Registered component key: "box", "section", "hero", … */
|
|
14
|
+
component: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional builder-only display name shown in the console's block tree. Not a
|
|
17
|
+
* render prop — the renderer ignores it; it only persists the author's rename.
|
|
18
|
+
* Absent means "use the component's human-readable name".
|
|
19
|
+
*/
|
|
20
|
+
label?: string;
|
|
21
|
+
/** Prop values as native JSON (unwrapped). */
|
|
22
|
+
props: Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Named child regions. Present only for components that declare slots.
|
|
25
|
+
* The default slot is named `children`; Section keys its columns "0", "1", …
|
|
26
|
+
*/
|
|
27
|
+
slots?: Record<string, PlenaryNode[]>;
|
|
28
|
+
}
|
|
29
|
+
/** The full component tree persisted in an entry's `dom` field. */
|
|
30
|
+
interface PlenaryDocument {
|
|
31
|
+
version: 1;
|
|
32
|
+
/** Top-level nodes. */
|
|
33
|
+
root: PlenaryNode[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Editor-origin matching — which console origins a site's preview bridge will
|
|
38
|
+
* speak to. A site lists patterns; every inbound bridge message must come from
|
|
39
|
+
* an origin matching one of them.
|
|
40
|
+
*
|
|
41
|
+
* Pattern forms:
|
|
42
|
+
*
|
|
43
|
+
* - Exact origin: `https://cms.example.com`, `http://localhost:5173`
|
|
44
|
+
* - Subdomain wildcard: `https://*.example.com` — matches any depth of
|
|
45
|
+
* subdomain (`a.example.com`, `a.b.example.com`) but never the bare apex.
|
|
46
|
+
* `*.` is only valid as the leftmost host label.
|
|
47
|
+
* - Port wildcard: `http://localhost:*` — matches any port, including none.
|
|
48
|
+
*
|
|
49
|
+
* The scheme is always literal (no wildcard), and a bare `*` host is not
|
|
50
|
+
* supported: allowing any origin would let any page that frames the site drive
|
|
51
|
+
* its preview. List the origins you trust, wildcarded no wider than a domain
|
|
52
|
+
* you control.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* True when `origin` (a browser origin string, e.g. a MessageEvent's `origin`)
|
|
56
|
+
* is allowed by any of `patterns`. Malformed patterns and origins match nothing.
|
|
57
|
+
*
|
|
58
|
+
* Browsers normalize origins to omit scheme-default ports, so a pattern without
|
|
59
|
+
* a port matches exactly the default-port origin, and an explicit port must
|
|
60
|
+
* match explicitly.
|
|
61
|
+
*/
|
|
62
|
+
declare function isAllowedEditorOrigin(patterns: readonly string[], origin: string): boolean;
|
|
63
|
+
/** True when any pattern uses a wildcard (and so cannot be a postMessage target). */
|
|
64
|
+
declare function hasWildcardPattern(patterns: readonly string[]): boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Component registration — how a site declares the components (built-in and
|
|
68
|
+
* custom) that its DOM tree may reference. The SDK normalizes these to the
|
|
69
|
+
* server's field-definition shape before announcing them over the bridge, so the
|
|
70
|
+
* console can drive its existing field inputs from a component's props.
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Field types allowed as an editable prop: every content field type **except**
|
|
74
|
+
* `object` and `computed`. `list` carries `fields` for repeating groups (e.g. a
|
|
75
|
+
* Hero's `slides`); `tags` remains the scalar-string repeating type. `form` is
|
|
76
|
+
* builder-only (not a content field type): a picker of the site's forms, whose
|
|
77
|
+
* value is `{ form_key }` and which the renderer resolves to a definition.
|
|
78
|
+
*/
|
|
79
|
+
type PropFieldType = "text" | "rich_text" | "number" | "boolean" | "date" | "time" | "datetime" | "json" | "image" | "file" | "reference" | "form" | "email" | "url" | "select" | "multi_select" | "list" | "tags";
|
|
80
|
+
/** An editable prop, a friendlier shorthand over the server's field definition. */
|
|
81
|
+
interface PropDefinition {
|
|
82
|
+
key: string;
|
|
83
|
+
type: PropFieldType;
|
|
84
|
+
required?: boolean;
|
|
85
|
+
defaultValue?: unknown;
|
|
86
|
+
/** Sub-fields for a `list` of groups (e.g. Hero's `slides`). */
|
|
87
|
+
fields?: PropDefinition[];
|
|
88
|
+
/** Maps to field `settings` (select options, min/max, …). */
|
|
89
|
+
settings?: Record<string, unknown>;
|
|
90
|
+
/** Maps to field-ui config (widget, placeholder, …). */
|
|
91
|
+
ui?: Record<string, unknown>;
|
|
92
|
+
/**
|
|
93
|
+
* Show this prop's input only while a sibling prop equals a value (exact
|
|
94
|
+
* equality). Purely a Properties-tab affordance — the prop still exists on
|
|
95
|
+
* the node and still reaches the component.
|
|
96
|
+
*/
|
|
97
|
+
visibleWhen?: {
|
|
98
|
+
prop: string;
|
|
99
|
+
equals: unknown;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** A named child region a component exposes for nesting other nodes. */
|
|
103
|
+
interface SlotDefinition {
|
|
104
|
+
/** "header", "body"; "children" for the single default slot. */
|
|
105
|
+
name: string;
|
|
106
|
+
label?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The style-editor panels a component can expose. Declaring them is a contract:
|
|
110
|
+
* the component applies the editor's `style` prop (a React `CSSProperties`
|
|
111
|
+
* object) to its root element, so what the panels edit actually renders.
|
|
112
|
+
*/
|
|
113
|
+
type StylePanel = "layout" | "size" | "background" | "typography" | "css";
|
|
114
|
+
/**
|
|
115
|
+
* One design token: a named value the site's stylesheet exposes as a CSS custom
|
|
116
|
+
* property (`--<name>`). The editor's style controls offer tokens first and
|
|
117
|
+
* store references (`var(--<name>)`), so a theme change reflows every page that
|
|
118
|
+
* used the token; `value` is only the editor's preview of what the site
|
|
119
|
+
* currently resolves it to.
|
|
120
|
+
*/
|
|
121
|
+
interface ThemeToken {
|
|
122
|
+
/** Custom property name without the leading `--` ("brand-primary"). */
|
|
123
|
+
name: string;
|
|
124
|
+
/** The resolved value, for editor previews ("#4f46e5", "Inter, sans-serif"). */
|
|
125
|
+
value: string;
|
|
126
|
+
/** Human label for pickers; defaults to the name. */
|
|
127
|
+
label?: string;
|
|
128
|
+
}
|
|
129
|
+
/** The design tokens a site offers the editor's style controls. */
|
|
130
|
+
interface ThemeRegistration {
|
|
131
|
+
/** Color tokens (color / background / border pickers). */
|
|
132
|
+
colors?: ThemeToken[];
|
|
133
|
+
/** Font stack tokens (the typography panel's font picker). */
|
|
134
|
+
fonts?: ThemeToken[];
|
|
135
|
+
/**
|
|
136
|
+
* `max-width` cutoffs (px) for the responsive style breakpoints; defaults
|
|
137
|
+
* tablet 1024 / mobile 640. The SDK compiles the editor's per-breakpoint
|
|
138
|
+
* styles against these, so they should agree with the site's own stylesheet.
|
|
139
|
+
*/
|
|
140
|
+
breakpoints?: {
|
|
141
|
+
tablet?: number;
|
|
142
|
+
mobile?: number;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** A component the site registers so the DOM tree may reference it by `key`. */
|
|
146
|
+
interface ComponentRegistration {
|
|
147
|
+
/** Unique key used in the DOM ("hero"). */
|
|
148
|
+
key: string;
|
|
149
|
+
/** Human label for the palette ("Hero Carousel"). */
|
|
150
|
+
label: string;
|
|
151
|
+
/** URL or data URI for the palette. */
|
|
152
|
+
icon?: string;
|
|
153
|
+
/** Editable props. */
|
|
154
|
+
props?: PropDefinition[];
|
|
155
|
+
/** Fixed named slots (custom components: static only in v1). */
|
|
156
|
+
slots?: SlotDefinition[];
|
|
157
|
+
/** Palette grouping. */
|
|
158
|
+
category?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Style panels the editor offers for this component. Declaring any panel is a
|
|
161
|
+
* promise to apply the editor's `style` prop to the component's root element.
|
|
162
|
+
* Omitted (or empty) means the component exposes no style controls — a
|
|
163
|
+
* legitimate choice for a locked-down component.
|
|
164
|
+
*/
|
|
165
|
+
styles?: StylePanel[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The responsive style model: three style layers per node, desktop-first.
|
|
170
|
+
*
|
|
171
|
+
* A node's editor styles live on its props as flat camelCase records —
|
|
172
|
+
* `style` (base), `styleTablet`, `styleMobile`. `tablet` overrides `base` at
|
|
173
|
+
* widths ≤ the tablet cutoff, `mobile` overrides both at ≤ the mobile cutoff;
|
|
174
|
+
* overrides are per-property, not per-object.
|
|
175
|
+
*
|
|
176
|
+
* The compiler turns those layers into per-node CSS targeting the renderer's
|
|
177
|
+
* annotation wrapper (`[data-plenary-id="…"] > *`). Every declaration is
|
|
178
|
+
* emitted `!important`: editor styles were historically inline (which beats
|
|
179
|
+
* all non-important site CSS), and the generated rules preserve exactly that
|
|
180
|
+
* precedence while still cascading correctly among themselves — the three
|
|
181
|
+
* rules share one specificity, so the later media block wins where it applies.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
type Breakpoint = "base" | "tablet" | "mobile";
|
|
185
|
+
declare const BREAKPOINTS: readonly Breakpoint[];
|
|
186
|
+
/** The node prop each breakpoint's style layer is stored in. */
|
|
187
|
+
declare const STYLE_PROP_FOR_BREAKPOINT: Record<Breakpoint, string>;
|
|
188
|
+
/** The `max-width` cutoffs (px) at which the override layers apply. */
|
|
189
|
+
interface BreakpointCutoffs {
|
|
190
|
+
tablet: number;
|
|
191
|
+
mobile: number;
|
|
192
|
+
}
|
|
193
|
+
declare const DEFAULT_CUTOFFS: BreakpointCutoffs;
|
|
194
|
+
/** A node's three style layers; absent layer = no styles there. */
|
|
195
|
+
interface NodeStyles {
|
|
196
|
+
base?: Record<string, string>;
|
|
197
|
+
tablet?: Record<string, string>;
|
|
198
|
+
mobile?: Record<string, string>;
|
|
199
|
+
}
|
|
200
|
+
/** Read a node's style layers off its props. */
|
|
201
|
+
declare function readNodeStyles(props: Record<string, unknown>): NodeStyles;
|
|
202
|
+
/**
|
|
203
|
+
* The effective (cascaded) style at a breakpoint: base, overlaid per-property
|
|
204
|
+
* by tablet (at tablet and below), then mobile (at mobile only).
|
|
205
|
+
*/
|
|
206
|
+
declare function effectiveStyle(styles: NodeStyles, breakpoint: Breakpoint): Record<string, string>;
|
|
207
|
+
/**
|
|
208
|
+
* The cutoffs a site's theme declares, defaulted and validated — a cutoff must
|
|
209
|
+
* be a positive finite number of px or it falls back to the default.
|
|
210
|
+
*/
|
|
211
|
+
declare function resolveCutoffs(theme: ThemeRegistration | undefined): BreakpointCutoffs;
|
|
212
|
+
/**
|
|
213
|
+
* Compile one node's style layers to CSS: a base rule plus a `@media
|
|
214
|
+
* (max-width)` block per override layer, in cascade order. Returns "" when no
|
|
215
|
+
* declaration survives — callers skip the node entirely.
|
|
216
|
+
*/
|
|
217
|
+
declare function compileNodeCss(id: string, styles: NodeStyles, cutoffs?: BreakpointCutoffs): string;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The postMessage protocol — the contract between a site's preview bridge
|
|
221
|
+
* (`@plenary/react`) and the console's page builder. These types are the single
|
|
222
|
+
* source of truth every framework adapter and the console share.
|
|
223
|
+
*
|
|
224
|
+
* Origin discipline is the security model: the bridge ignores any message whose
|
|
225
|
+
* origin is not in its configured `editorOrigins`, and the console ignores any
|
|
226
|
+
* message not from the iframe's known origin.
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/** Current protocol version; negotiated in the handshake. Bumped on breaking changes. */
|
|
230
|
+
declare const PROTOCOL_VERSION = 1;
|
|
231
|
+
/**
|
|
232
|
+
* Drag data-transfer key for a new component dragged from the console palette.
|
|
233
|
+
* The bridge reads it on `drop` over the live preview to know which component to
|
|
234
|
+
* insert. Kept in sync with the console's own constant.
|
|
235
|
+
*/
|
|
236
|
+
declare const DND_NEW = "application/x-plenary-new";
|
|
237
|
+
/** Every message on the wire is wrapped in this envelope. */
|
|
238
|
+
interface PlenaryMessage<TType extends string, TPayload> {
|
|
239
|
+
/** Discriminates Plenary traffic from other postMessage senders. */
|
|
240
|
+
plenary: true;
|
|
241
|
+
/** Protocol version of the sender. */
|
|
242
|
+
v: number;
|
|
243
|
+
type: TType;
|
|
244
|
+
payload: TPayload;
|
|
245
|
+
}
|
|
246
|
+
/** A component as announced in the manifest (normalized from its registration). */
|
|
247
|
+
interface ManifestComponent {
|
|
248
|
+
/** Registration key used in the DOM ("hero"). */
|
|
249
|
+
name: string;
|
|
250
|
+
label: string;
|
|
251
|
+
icon?: string;
|
|
252
|
+
props: PropDefinition[];
|
|
253
|
+
slots: SlotDefinition[];
|
|
254
|
+
category?: string;
|
|
255
|
+
/** Style panels the component exposes; absent means none. */
|
|
256
|
+
styles?: StylePanel[];
|
|
257
|
+
}
|
|
258
|
+
/** `hello` — bridge announces it is ready. */
|
|
259
|
+
interface HelloPayload {
|
|
260
|
+
protocolVersion: number;
|
|
261
|
+
sdkVersion: string;
|
|
262
|
+
}
|
|
263
|
+
/** `init` — console acks the handshake and supplies context. */
|
|
264
|
+
interface InitPayload {
|
|
265
|
+
entryId: string;
|
|
266
|
+
modelKey: string;
|
|
267
|
+
editorOrigin: string;
|
|
268
|
+
}
|
|
269
|
+
/** `manifest` — the full component catalog (built-ins + custom) and theme. */
|
|
270
|
+
interface ManifestPayload {
|
|
271
|
+
components: ManifestComponent[];
|
|
272
|
+
/** The site's design tokens, when it registered any. */
|
|
273
|
+
theme?: ThemeRegistration;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* `render` — the draft entry the bridge should render instead of the API's copy.
|
|
277
|
+
* A component tree lives in `data` as a `blocks` field; the bridge renders the
|
|
278
|
+
* field named by its `region`.
|
|
279
|
+
*/
|
|
280
|
+
interface RenderPayload {
|
|
281
|
+
data: Record<string, unknown>;
|
|
282
|
+
locale: string;
|
|
283
|
+
}
|
|
284
|
+
/** `patch` — a targeted prop update (optimization; v1 may just re-send `render`). */
|
|
285
|
+
interface PatchPayload {
|
|
286
|
+
nodeId: string;
|
|
287
|
+
props: Record<string, unknown>;
|
|
288
|
+
}
|
|
289
|
+
/** `select` — user clicked a node in the preview. */
|
|
290
|
+
interface SelectPayload {
|
|
291
|
+
nodeId: string;
|
|
292
|
+
}
|
|
293
|
+
/** `hover` — pointer moved over a node (or off all nodes). */
|
|
294
|
+
interface HoverPayload {
|
|
295
|
+
nodeId: string | null;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `highlight` — console asks the preview to outline nodes from the sidebar.
|
|
299
|
+
* Two independent channels, drawn with distinct styles: `selectedId` is the
|
|
300
|
+
* block being edited (strong outline), `hoverId` is whatever the cursor is over
|
|
301
|
+
* — a tree row or a preview element — (lighter outline). Either may be null.
|
|
302
|
+
*/
|
|
303
|
+
interface HighlightPayload {
|
|
304
|
+
selectedId: string | null;
|
|
305
|
+
hoverId: string | null;
|
|
306
|
+
}
|
|
307
|
+
/** Where a component dropped on the live canvas should land, in tree terms. */
|
|
308
|
+
type CanvasDropTarget = {
|
|
309
|
+
at: "root";
|
|
310
|
+
} | {
|
|
311
|
+
at: "before" | "after";
|
|
312
|
+
nodeId: string;
|
|
313
|
+
} | {
|
|
314
|
+
at: "slot";
|
|
315
|
+
nodeId: string;
|
|
316
|
+
slot: string;
|
|
317
|
+
};
|
|
318
|
+
/** `canvasDrop` — a palette component was dropped onto the rendered preview. */
|
|
319
|
+
interface CanvasDropPayload {
|
|
320
|
+
component: string;
|
|
321
|
+
target: CanvasDropTarget;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* `size` — the page's full content height in CSS px, reported by the bridge on
|
|
325
|
+
* load and whenever it changes. The console's artboard sizes the preview iframe
|
|
326
|
+
* to it so the whole page is visible with no inner scrollbar.
|
|
327
|
+
*/
|
|
328
|
+
interface SizePayload {
|
|
329
|
+
height: number;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* `wheel` — a Ctrl/Cmd+wheel zoom gesture performed over the page. The iframe
|
|
333
|
+
* consumes wheel events, so the bridge forwards them for the console's artboard
|
|
334
|
+
* to zoom; `x`/`y` are the cursor in page (iframe-viewport) coordinates, which
|
|
335
|
+
* are the artboard's unscaled frame coordinates.
|
|
336
|
+
*/
|
|
337
|
+
interface WheelPayload {
|
|
338
|
+
deltaY: number;
|
|
339
|
+
x: number;
|
|
340
|
+
y: number;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* `pan` — a middle-button drag step over the page (the hand tool). `dx`/`dy`
|
|
344
|
+
* are the pointer's movement since the last step, in page coordinates.
|
|
345
|
+
*/
|
|
346
|
+
interface PanPayload {
|
|
347
|
+
dx: number;
|
|
348
|
+
dy: number;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* `key` — an editor keyboard shortcut pressed while focus is inside the frame
|
|
352
|
+
* (clicking a block focuses the iframe, so the console's own listener never
|
|
353
|
+
* sees these). The bridge forwards only recognized shortcut combinations and
|
|
354
|
+
* prevents their default in the frame; the console dispatches them exactly as
|
|
355
|
+
* if they had been pressed on its window.
|
|
356
|
+
*/
|
|
357
|
+
interface KeyPayload {
|
|
358
|
+
key: string;
|
|
359
|
+
ctrlKey: boolean;
|
|
360
|
+
metaKey: boolean;
|
|
361
|
+
shiftKey: boolean;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* `edit` — an in-place edit committed inside the frame (double-click inline
|
|
365
|
+
* text editing). The bridge has already applied it to its local draft; the
|
|
366
|
+
* console applies it to the real draft as one undoable step and its next
|
|
367
|
+
* `render` echo matches what the frame is showing.
|
|
368
|
+
*/
|
|
369
|
+
interface EditPayload {
|
|
370
|
+
nodeId: string;
|
|
371
|
+
props: Record<string, unknown>;
|
|
372
|
+
}
|
|
373
|
+
type HelloMessage = PlenaryMessage<"hello", HelloPayload>;
|
|
374
|
+
type InitMessage = PlenaryMessage<"init", InitPayload>;
|
|
375
|
+
type ManifestMessage = PlenaryMessage<"manifest", ManifestPayload>;
|
|
376
|
+
type RenderMessage = PlenaryMessage<"render", RenderPayload>;
|
|
377
|
+
type PatchMessage = PlenaryMessage<"patch", PatchPayload>;
|
|
378
|
+
type SelectMessage = PlenaryMessage<"select", SelectPayload>;
|
|
379
|
+
type HoverMessage = PlenaryMessage<"hover", HoverPayload>;
|
|
380
|
+
type HighlightMessage = PlenaryMessage<"highlight", HighlightPayload>;
|
|
381
|
+
type CanvasDropMessage = PlenaryMessage<"canvasDrop", CanvasDropPayload>;
|
|
382
|
+
type SizeMessage = PlenaryMessage<"size", SizePayload>;
|
|
383
|
+
type WheelMessage = PlenaryMessage<"wheel", WheelPayload>;
|
|
384
|
+
type PanMessage = PlenaryMessage<"pan", PanPayload>;
|
|
385
|
+
type KeyMessage = PlenaryMessage<"key", KeyPayload>;
|
|
386
|
+
type EditMessage = PlenaryMessage<"edit", EditPayload>;
|
|
387
|
+
/** Messages the bridge (iframe) sends up to the console. */
|
|
388
|
+
type IframeToConsoleMessage = HelloMessage | ManifestMessage | SelectMessage | HoverMessage | CanvasDropMessage | SizeMessage | WheelMessage | PanMessage | KeyMessage | EditMessage;
|
|
389
|
+
/** Messages the console sends down to the bridge (iframe). */
|
|
390
|
+
type ConsoleToIframeMessage = InitMessage | RenderMessage | PatchMessage | HighlightMessage;
|
|
391
|
+
/** Any message in the protocol, either direction. */
|
|
392
|
+
type PlenaryProtocolMessage = IframeToConsoleMessage | ConsoleToIframeMessage;
|
|
393
|
+
/** Maps each message `type` to its payload — useful for typed senders/handlers. */
|
|
394
|
+
interface PlenaryMessagePayloads {
|
|
395
|
+
hello: HelloPayload;
|
|
396
|
+
init: InitPayload;
|
|
397
|
+
manifest: ManifestPayload;
|
|
398
|
+
render: RenderPayload;
|
|
399
|
+
patch: PatchPayload;
|
|
400
|
+
select: SelectPayload;
|
|
401
|
+
hover: HoverPayload;
|
|
402
|
+
highlight: HighlightPayload;
|
|
403
|
+
canvasDrop: CanvasDropPayload;
|
|
404
|
+
size: SizePayload;
|
|
405
|
+
wheel: WheelPayload;
|
|
406
|
+
pan: PanPayload;
|
|
407
|
+
key: KeyPayload;
|
|
408
|
+
edit: EditPayload;
|
|
409
|
+
}
|
|
410
|
+
/** Every protocol message `type` string. */
|
|
411
|
+
type PlenaryMessageType = keyof PlenaryMessagePayloads;
|
|
412
|
+
|
|
413
|
+
export { BREAKPOINTS, type Breakpoint, type BreakpointCutoffs, type CanvasDropMessage, type CanvasDropPayload, type CanvasDropTarget, type ComponentRegistration, type ConsoleToIframeMessage, DEFAULT_CUTOFFS, DND_NEW, type EditMessage, type EditPayload, type HelloMessage, type HelloPayload, type HighlightMessage, type HighlightPayload, type HoverMessage, type HoverPayload, type IframeToConsoleMessage, type InitMessage, type InitPayload, type KeyMessage, type KeyPayload, type ManifestComponent, type ManifestMessage, type ManifestPayload, type NodeStyles, PROTOCOL_VERSION, type PanMessage, type PanPayload, type PatchMessage, type PatchPayload, type PlenaryDocument, type PlenaryMessage, type PlenaryMessagePayloads, type PlenaryMessageType, type PlenaryNode, type PlenaryProtocolMessage, type PropDefinition, type PropFieldType, type RenderMessage, type RenderPayload, STYLE_PROP_FOR_BREAKPOINT, type SelectMessage, type SelectPayload, type SizeMessage, type SizePayload, type SlotDefinition, type StylePanel, type ThemeRegistration, type ThemeToken, type WheelMessage, type WheelPayload, compileNodeCss, effectiveStyle, hasWildcardPattern, isAllowedEditorOrigin, readNodeStyles, resolveCutoffs };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DOM node model — the serialized component tree stored in an entry's `dom`
|
|
3
|
+
* field and rendered by a framework adapter (`@plenary/react`).
|
|
4
|
+
*
|
|
5
|
+
* Built-in and custom components are indistinguishable in the tree: every node
|
|
6
|
+
* references a registered component by name. That is the whole point of
|
|
7
|
+
* pre-registering the primitives.
|
|
8
|
+
*/
|
|
9
|
+
/** A single node in a Page's component tree. */
|
|
10
|
+
interface PlenaryNode {
|
|
11
|
+
/** Stable id minted by the console; survives reorders. The SDK never generates ids. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Registered component key: "box", "section", "hero", … */
|
|
14
|
+
component: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional builder-only display name shown in the console's block tree. Not a
|
|
17
|
+
* render prop — the renderer ignores it; it only persists the author's rename.
|
|
18
|
+
* Absent means "use the component's human-readable name".
|
|
19
|
+
*/
|
|
20
|
+
label?: string;
|
|
21
|
+
/** Prop values as native JSON (unwrapped). */
|
|
22
|
+
props: Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Named child regions. Present only for components that declare slots.
|
|
25
|
+
* The default slot is named `children`; Section keys its columns "0", "1", …
|
|
26
|
+
*/
|
|
27
|
+
slots?: Record<string, PlenaryNode[]>;
|
|
28
|
+
}
|
|
29
|
+
/** The full component tree persisted in an entry's `dom` field. */
|
|
30
|
+
interface PlenaryDocument {
|
|
31
|
+
version: 1;
|
|
32
|
+
/** Top-level nodes. */
|
|
33
|
+
root: PlenaryNode[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Editor-origin matching — which console origins a site's preview bridge will
|
|
38
|
+
* speak to. A site lists patterns; every inbound bridge message must come from
|
|
39
|
+
* an origin matching one of them.
|
|
40
|
+
*
|
|
41
|
+
* Pattern forms:
|
|
42
|
+
*
|
|
43
|
+
* - Exact origin: `https://cms.example.com`, `http://localhost:5173`
|
|
44
|
+
* - Subdomain wildcard: `https://*.example.com` — matches any depth of
|
|
45
|
+
* subdomain (`a.example.com`, `a.b.example.com`) but never the bare apex.
|
|
46
|
+
* `*.` is only valid as the leftmost host label.
|
|
47
|
+
* - Port wildcard: `http://localhost:*` — matches any port, including none.
|
|
48
|
+
*
|
|
49
|
+
* The scheme is always literal (no wildcard), and a bare `*` host is not
|
|
50
|
+
* supported: allowing any origin would let any page that frames the site drive
|
|
51
|
+
* its preview. List the origins you trust, wildcarded no wider than a domain
|
|
52
|
+
* you control.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* True when `origin` (a browser origin string, e.g. a MessageEvent's `origin`)
|
|
56
|
+
* is allowed by any of `patterns`. Malformed patterns and origins match nothing.
|
|
57
|
+
*
|
|
58
|
+
* Browsers normalize origins to omit scheme-default ports, so a pattern without
|
|
59
|
+
* a port matches exactly the default-port origin, and an explicit port must
|
|
60
|
+
* match explicitly.
|
|
61
|
+
*/
|
|
62
|
+
declare function isAllowedEditorOrigin(patterns: readonly string[], origin: string): boolean;
|
|
63
|
+
/** True when any pattern uses a wildcard (and so cannot be a postMessage target). */
|
|
64
|
+
declare function hasWildcardPattern(patterns: readonly string[]): boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Component registration — how a site declares the components (built-in and
|
|
68
|
+
* custom) that its DOM tree may reference. The SDK normalizes these to the
|
|
69
|
+
* server's field-definition shape before announcing them over the bridge, so the
|
|
70
|
+
* console can drive its existing field inputs from a component's props.
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Field types allowed as an editable prop: every content field type **except**
|
|
74
|
+
* `object` and `computed`. `list` carries `fields` for repeating groups (e.g. a
|
|
75
|
+
* Hero's `slides`); `tags` remains the scalar-string repeating type. `form` is
|
|
76
|
+
* builder-only (not a content field type): a picker of the site's forms, whose
|
|
77
|
+
* value is `{ form_key }` and which the renderer resolves to a definition.
|
|
78
|
+
*/
|
|
79
|
+
type PropFieldType = "text" | "rich_text" | "number" | "boolean" | "date" | "time" | "datetime" | "json" | "image" | "file" | "reference" | "form" | "email" | "url" | "select" | "multi_select" | "list" | "tags";
|
|
80
|
+
/** An editable prop, a friendlier shorthand over the server's field definition. */
|
|
81
|
+
interface PropDefinition {
|
|
82
|
+
key: string;
|
|
83
|
+
type: PropFieldType;
|
|
84
|
+
required?: boolean;
|
|
85
|
+
defaultValue?: unknown;
|
|
86
|
+
/** Sub-fields for a `list` of groups (e.g. Hero's `slides`). */
|
|
87
|
+
fields?: PropDefinition[];
|
|
88
|
+
/** Maps to field `settings` (select options, min/max, …). */
|
|
89
|
+
settings?: Record<string, unknown>;
|
|
90
|
+
/** Maps to field-ui config (widget, placeholder, …). */
|
|
91
|
+
ui?: Record<string, unknown>;
|
|
92
|
+
/**
|
|
93
|
+
* Show this prop's input only while a sibling prop equals a value (exact
|
|
94
|
+
* equality). Purely a Properties-tab affordance — the prop still exists on
|
|
95
|
+
* the node and still reaches the component.
|
|
96
|
+
*/
|
|
97
|
+
visibleWhen?: {
|
|
98
|
+
prop: string;
|
|
99
|
+
equals: unknown;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** A named child region a component exposes for nesting other nodes. */
|
|
103
|
+
interface SlotDefinition {
|
|
104
|
+
/** "header", "body"; "children" for the single default slot. */
|
|
105
|
+
name: string;
|
|
106
|
+
label?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The style-editor panels a component can expose. Declaring them is a contract:
|
|
110
|
+
* the component applies the editor's `style` prop (a React `CSSProperties`
|
|
111
|
+
* object) to its root element, so what the panels edit actually renders.
|
|
112
|
+
*/
|
|
113
|
+
type StylePanel = "layout" | "size" | "background" | "typography" | "css";
|
|
114
|
+
/**
|
|
115
|
+
* One design token: a named value the site's stylesheet exposes as a CSS custom
|
|
116
|
+
* property (`--<name>`). The editor's style controls offer tokens first and
|
|
117
|
+
* store references (`var(--<name>)`), so a theme change reflows every page that
|
|
118
|
+
* used the token; `value` is only the editor's preview of what the site
|
|
119
|
+
* currently resolves it to.
|
|
120
|
+
*/
|
|
121
|
+
interface ThemeToken {
|
|
122
|
+
/** Custom property name without the leading `--` ("brand-primary"). */
|
|
123
|
+
name: string;
|
|
124
|
+
/** The resolved value, for editor previews ("#4f46e5", "Inter, sans-serif"). */
|
|
125
|
+
value: string;
|
|
126
|
+
/** Human label for pickers; defaults to the name. */
|
|
127
|
+
label?: string;
|
|
128
|
+
}
|
|
129
|
+
/** The design tokens a site offers the editor's style controls. */
|
|
130
|
+
interface ThemeRegistration {
|
|
131
|
+
/** Color tokens (color / background / border pickers). */
|
|
132
|
+
colors?: ThemeToken[];
|
|
133
|
+
/** Font stack tokens (the typography panel's font picker). */
|
|
134
|
+
fonts?: ThemeToken[];
|
|
135
|
+
/**
|
|
136
|
+
* `max-width` cutoffs (px) for the responsive style breakpoints; defaults
|
|
137
|
+
* tablet 1024 / mobile 640. The SDK compiles the editor's per-breakpoint
|
|
138
|
+
* styles against these, so they should agree with the site's own stylesheet.
|
|
139
|
+
*/
|
|
140
|
+
breakpoints?: {
|
|
141
|
+
tablet?: number;
|
|
142
|
+
mobile?: number;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** A component the site registers so the DOM tree may reference it by `key`. */
|
|
146
|
+
interface ComponentRegistration {
|
|
147
|
+
/** Unique key used in the DOM ("hero"). */
|
|
148
|
+
key: string;
|
|
149
|
+
/** Human label for the palette ("Hero Carousel"). */
|
|
150
|
+
label: string;
|
|
151
|
+
/** URL or data URI for the palette. */
|
|
152
|
+
icon?: string;
|
|
153
|
+
/** Editable props. */
|
|
154
|
+
props?: PropDefinition[];
|
|
155
|
+
/** Fixed named slots (custom components: static only in v1). */
|
|
156
|
+
slots?: SlotDefinition[];
|
|
157
|
+
/** Palette grouping. */
|
|
158
|
+
category?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Style panels the editor offers for this component. Declaring any panel is a
|
|
161
|
+
* promise to apply the editor's `style` prop to the component's root element.
|
|
162
|
+
* Omitted (or empty) means the component exposes no style controls — a
|
|
163
|
+
* legitimate choice for a locked-down component.
|
|
164
|
+
*/
|
|
165
|
+
styles?: StylePanel[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The responsive style model: three style layers per node, desktop-first.
|
|
170
|
+
*
|
|
171
|
+
* A node's editor styles live on its props as flat camelCase records —
|
|
172
|
+
* `style` (base), `styleTablet`, `styleMobile`. `tablet` overrides `base` at
|
|
173
|
+
* widths ≤ the tablet cutoff, `mobile` overrides both at ≤ the mobile cutoff;
|
|
174
|
+
* overrides are per-property, not per-object.
|
|
175
|
+
*
|
|
176
|
+
* The compiler turns those layers into per-node CSS targeting the renderer's
|
|
177
|
+
* annotation wrapper (`[data-plenary-id="…"] > *`). Every declaration is
|
|
178
|
+
* emitted `!important`: editor styles were historically inline (which beats
|
|
179
|
+
* all non-important site CSS), and the generated rules preserve exactly that
|
|
180
|
+
* precedence while still cascading correctly among themselves — the three
|
|
181
|
+
* rules share one specificity, so the later media block wins where it applies.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
type Breakpoint = "base" | "tablet" | "mobile";
|
|
185
|
+
declare const BREAKPOINTS: readonly Breakpoint[];
|
|
186
|
+
/** The node prop each breakpoint's style layer is stored in. */
|
|
187
|
+
declare const STYLE_PROP_FOR_BREAKPOINT: Record<Breakpoint, string>;
|
|
188
|
+
/** The `max-width` cutoffs (px) at which the override layers apply. */
|
|
189
|
+
interface BreakpointCutoffs {
|
|
190
|
+
tablet: number;
|
|
191
|
+
mobile: number;
|
|
192
|
+
}
|
|
193
|
+
declare const DEFAULT_CUTOFFS: BreakpointCutoffs;
|
|
194
|
+
/** A node's three style layers; absent layer = no styles there. */
|
|
195
|
+
interface NodeStyles {
|
|
196
|
+
base?: Record<string, string>;
|
|
197
|
+
tablet?: Record<string, string>;
|
|
198
|
+
mobile?: Record<string, string>;
|
|
199
|
+
}
|
|
200
|
+
/** Read a node's style layers off its props. */
|
|
201
|
+
declare function readNodeStyles(props: Record<string, unknown>): NodeStyles;
|
|
202
|
+
/**
|
|
203
|
+
* The effective (cascaded) style at a breakpoint: base, overlaid per-property
|
|
204
|
+
* by tablet (at tablet and below), then mobile (at mobile only).
|
|
205
|
+
*/
|
|
206
|
+
declare function effectiveStyle(styles: NodeStyles, breakpoint: Breakpoint): Record<string, string>;
|
|
207
|
+
/**
|
|
208
|
+
* The cutoffs a site's theme declares, defaulted and validated — a cutoff must
|
|
209
|
+
* be a positive finite number of px or it falls back to the default.
|
|
210
|
+
*/
|
|
211
|
+
declare function resolveCutoffs(theme: ThemeRegistration | undefined): BreakpointCutoffs;
|
|
212
|
+
/**
|
|
213
|
+
* Compile one node's style layers to CSS: a base rule plus a `@media
|
|
214
|
+
* (max-width)` block per override layer, in cascade order. Returns "" when no
|
|
215
|
+
* declaration survives — callers skip the node entirely.
|
|
216
|
+
*/
|
|
217
|
+
declare function compileNodeCss(id: string, styles: NodeStyles, cutoffs?: BreakpointCutoffs): string;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The postMessage protocol — the contract between a site's preview bridge
|
|
221
|
+
* (`@plenary/react`) and the console's page builder. These types are the single
|
|
222
|
+
* source of truth every framework adapter and the console share.
|
|
223
|
+
*
|
|
224
|
+
* Origin discipline is the security model: the bridge ignores any message whose
|
|
225
|
+
* origin is not in its configured `editorOrigins`, and the console ignores any
|
|
226
|
+
* message not from the iframe's known origin.
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/** Current protocol version; negotiated in the handshake. Bumped on breaking changes. */
|
|
230
|
+
declare const PROTOCOL_VERSION = 1;
|
|
231
|
+
/**
|
|
232
|
+
* Drag data-transfer key for a new component dragged from the console palette.
|
|
233
|
+
* The bridge reads it on `drop` over the live preview to know which component to
|
|
234
|
+
* insert. Kept in sync with the console's own constant.
|
|
235
|
+
*/
|
|
236
|
+
declare const DND_NEW = "application/x-plenary-new";
|
|
237
|
+
/** Every message on the wire is wrapped in this envelope. */
|
|
238
|
+
interface PlenaryMessage<TType extends string, TPayload> {
|
|
239
|
+
/** Discriminates Plenary traffic from other postMessage senders. */
|
|
240
|
+
plenary: true;
|
|
241
|
+
/** Protocol version of the sender. */
|
|
242
|
+
v: number;
|
|
243
|
+
type: TType;
|
|
244
|
+
payload: TPayload;
|
|
245
|
+
}
|
|
246
|
+
/** A component as announced in the manifest (normalized from its registration). */
|
|
247
|
+
interface ManifestComponent {
|
|
248
|
+
/** Registration key used in the DOM ("hero"). */
|
|
249
|
+
name: string;
|
|
250
|
+
label: string;
|
|
251
|
+
icon?: string;
|
|
252
|
+
props: PropDefinition[];
|
|
253
|
+
slots: SlotDefinition[];
|
|
254
|
+
category?: string;
|
|
255
|
+
/** Style panels the component exposes; absent means none. */
|
|
256
|
+
styles?: StylePanel[];
|
|
257
|
+
}
|
|
258
|
+
/** `hello` — bridge announces it is ready. */
|
|
259
|
+
interface HelloPayload {
|
|
260
|
+
protocolVersion: number;
|
|
261
|
+
sdkVersion: string;
|
|
262
|
+
}
|
|
263
|
+
/** `init` — console acks the handshake and supplies context. */
|
|
264
|
+
interface InitPayload {
|
|
265
|
+
entryId: string;
|
|
266
|
+
modelKey: string;
|
|
267
|
+
editorOrigin: string;
|
|
268
|
+
}
|
|
269
|
+
/** `manifest` — the full component catalog (built-ins + custom) and theme. */
|
|
270
|
+
interface ManifestPayload {
|
|
271
|
+
components: ManifestComponent[];
|
|
272
|
+
/** The site's design tokens, when it registered any. */
|
|
273
|
+
theme?: ThemeRegistration;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* `render` — the draft entry the bridge should render instead of the API's copy.
|
|
277
|
+
* A component tree lives in `data` as a `blocks` field; the bridge renders the
|
|
278
|
+
* field named by its `region`.
|
|
279
|
+
*/
|
|
280
|
+
interface RenderPayload {
|
|
281
|
+
data: Record<string, unknown>;
|
|
282
|
+
locale: string;
|
|
283
|
+
}
|
|
284
|
+
/** `patch` — a targeted prop update (optimization; v1 may just re-send `render`). */
|
|
285
|
+
interface PatchPayload {
|
|
286
|
+
nodeId: string;
|
|
287
|
+
props: Record<string, unknown>;
|
|
288
|
+
}
|
|
289
|
+
/** `select` — user clicked a node in the preview. */
|
|
290
|
+
interface SelectPayload {
|
|
291
|
+
nodeId: string;
|
|
292
|
+
}
|
|
293
|
+
/** `hover` — pointer moved over a node (or off all nodes). */
|
|
294
|
+
interface HoverPayload {
|
|
295
|
+
nodeId: string | null;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `highlight` — console asks the preview to outline nodes from the sidebar.
|
|
299
|
+
* Two independent channels, drawn with distinct styles: `selectedId` is the
|
|
300
|
+
* block being edited (strong outline), `hoverId` is whatever the cursor is over
|
|
301
|
+
* — a tree row or a preview element — (lighter outline). Either may be null.
|
|
302
|
+
*/
|
|
303
|
+
interface HighlightPayload {
|
|
304
|
+
selectedId: string | null;
|
|
305
|
+
hoverId: string | null;
|
|
306
|
+
}
|
|
307
|
+
/** Where a component dropped on the live canvas should land, in tree terms. */
|
|
308
|
+
type CanvasDropTarget = {
|
|
309
|
+
at: "root";
|
|
310
|
+
} | {
|
|
311
|
+
at: "before" | "after";
|
|
312
|
+
nodeId: string;
|
|
313
|
+
} | {
|
|
314
|
+
at: "slot";
|
|
315
|
+
nodeId: string;
|
|
316
|
+
slot: string;
|
|
317
|
+
};
|
|
318
|
+
/** `canvasDrop` — a palette component was dropped onto the rendered preview. */
|
|
319
|
+
interface CanvasDropPayload {
|
|
320
|
+
component: string;
|
|
321
|
+
target: CanvasDropTarget;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* `size` — the page's full content height in CSS px, reported by the bridge on
|
|
325
|
+
* load and whenever it changes. The console's artboard sizes the preview iframe
|
|
326
|
+
* to it so the whole page is visible with no inner scrollbar.
|
|
327
|
+
*/
|
|
328
|
+
interface SizePayload {
|
|
329
|
+
height: number;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* `wheel` — a Ctrl/Cmd+wheel zoom gesture performed over the page. The iframe
|
|
333
|
+
* consumes wheel events, so the bridge forwards them for the console's artboard
|
|
334
|
+
* to zoom; `x`/`y` are the cursor in page (iframe-viewport) coordinates, which
|
|
335
|
+
* are the artboard's unscaled frame coordinates.
|
|
336
|
+
*/
|
|
337
|
+
interface WheelPayload {
|
|
338
|
+
deltaY: number;
|
|
339
|
+
x: number;
|
|
340
|
+
y: number;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* `pan` — a middle-button drag step over the page (the hand tool). `dx`/`dy`
|
|
344
|
+
* are the pointer's movement since the last step, in page coordinates.
|
|
345
|
+
*/
|
|
346
|
+
interface PanPayload {
|
|
347
|
+
dx: number;
|
|
348
|
+
dy: number;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* `key` — an editor keyboard shortcut pressed while focus is inside the frame
|
|
352
|
+
* (clicking a block focuses the iframe, so the console's own listener never
|
|
353
|
+
* sees these). The bridge forwards only recognized shortcut combinations and
|
|
354
|
+
* prevents their default in the frame; the console dispatches them exactly as
|
|
355
|
+
* if they had been pressed on its window.
|
|
356
|
+
*/
|
|
357
|
+
interface KeyPayload {
|
|
358
|
+
key: string;
|
|
359
|
+
ctrlKey: boolean;
|
|
360
|
+
metaKey: boolean;
|
|
361
|
+
shiftKey: boolean;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* `edit` — an in-place edit committed inside the frame (double-click inline
|
|
365
|
+
* text editing). The bridge has already applied it to its local draft; the
|
|
366
|
+
* console applies it to the real draft as one undoable step and its next
|
|
367
|
+
* `render` echo matches what the frame is showing.
|
|
368
|
+
*/
|
|
369
|
+
interface EditPayload {
|
|
370
|
+
nodeId: string;
|
|
371
|
+
props: Record<string, unknown>;
|
|
372
|
+
}
|
|
373
|
+
type HelloMessage = PlenaryMessage<"hello", HelloPayload>;
|
|
374
|
+
type InitMessage = PlenaryMessage<"init", InitPayload>;
|
|
375
|
+
type ManifestMessage = PlenaryMessage<"manifest", ManifestPayload>;
|
|
376
|
+
type RenderMessage = PlenaryMessage<"render", RenderPayload>;
|
|
377
|
+
type PatchMessage = PlenaryMessage<"patch", PatchPayload>;
|
|
378
|
+
type SelectMessage = PlenaryMessage<"select", SelectPayload>;
|
|
379
|
+
type HoverMessage = PlenaryMessage<"hover", HoverPayload>;
|
|
380
|
+
type HighlightMessage = PlenaryMessage<"highlight", HighlightPayload>;
|
|
381
|
+
type CanvasDropMessage = PlenaryMessage<"canvasDrop", CanvasDropPayload>;
|
|
382
|
+
type SizeMessage = PlenaryMessage<"size", SizePayload>;
|
|
383
|
+
type WheelMessage = PlenaryMessage<"wheel", WheelPayload>;
|
|
384
|
+
type PanMessage = PlenaryMessage<"pan", PanPayload>;
|
|
385
|
+
type KeyMessage = PlenaryMessage<"key", KeyPayload>;
|
|
386
|
+
type EditMessage = PlenaryMessage<"edit", EditPayload>;
|
|
387
|
+
/** Messages the bridge (iframe) sends up to the console. */
|
|
388
|
+
type IframeToConsoleMessage = HelloMessage | ManifestMessage | SelectMessage | HoverMessage | CanvasDropMessage | SizeMessage | WheelMessage | PanMessage | KeyMessage | EditMessage;
|
|
389
|
+
/** Messages the console sends down to the bridge (iframe). */
|
|
390
|
+
type ConsoleToIframeMessage = InitMessage | RenderMessage | PatchMessage | HighlightMessage;
|
|
391
|
+
/** Any message in the protocol, either direction. */
|
|
392
|
+
type PlenaryProtocolMessage = IframeToConsoleMessage | ConsoleToIframeMessage;
|
|
393
|
+
/** Maps each message `type` to its payload — useful for typed senders/handlers. */
|
|
394
|
+
interface PlenaryMessagePayloads {
|
|
395
|
+
hello: HelloPayload;
|
|
396
|
+
init: InitPayload;
|
|
397
|
+
manifest: ManifestPayload;
|
|
398
|
+
render: RenderPayload;
|
|
399
|
+
patch: PatchPayload;
|
|
400
|
+
select: SelectPayload;
|
|
401
|
+
hover: HoverPayload;
|
|
402
|
+
highlight: HighlightPayload;
|
|
403
|
+
canvasDrop: CanvasDropPayload;
|
|
404
|
+
size: SizePayload;
|
|
405
|
+
wheel: WheelPayload;
|
|
406
|
+
pan: PanPayload;
|
|
407
|
+
key: KeyPayload;
|
|
408
|
+
edit: EditPayload;
|
|
409
|
+
}
|
|
410
|
+
/** Every protocol message `type` string. */
|
|
411
|
+
type PlenaryMessageType = keyof PlenaryMessagePayloads;
|
|
412
|
+
|
|
413
|
+
export { BREAKPOINTS, type Breakpoint, type BreakpointCutoffs, type CanvasDropMessage, type CanvasDropPayload, type CanvasDropTarget, type ComponentRegistration, type ConsoleToIframeMessage, DEFAULT_CUTOFFS, DND_NEW, type EditMessage, type EditPayload, type HelloMessage, type HelloPayload, type HighlightMessage, type HighlightPayload, type HoverMessage, type HoverPayload, type IframeToConsoleMessage, type InitMessage, type InitPayload, type KeyMessage, type KeyPayload, type ManifestComponent, type ManifestMessage, type ManifestPayload, type NodeStyles, PROTOCOL_VERSION, type PanMessage, type PanPayload, type PatchMessage, type PatchPayload, type PlenaryDocument, type PlenaryMessage, type PlenaryMessagePayloads, type PlenaryMessageType, type PlenaryNode, type PlenaryProtocolMessage, type PropDefinition, type PropFieldType, type RenderMessage, type RenderPayload, STYLE_PROP_FOR_BREAKPOINT, type SelectMessage, type SelectPayload, type SizeMessage, type SizePayload, type SlotDefinition, type StylePanel, type ThemeRegistration, type ThemeToken, type WheelMessage, type WheelPayload, compileNodeCss, effectiveStyle, hasWildcardPattern, isAllowedEditorOrigin, readNodeStyles, resolveCutoffs };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/origins.ts
|
|
2
|
+
function parsePattern(pattern) {
|
|
3
|
+
const match = pattern.trim().toLowerCase().match(/^([a-z][a-z0-9+.-]*):\/\/([^/:*]+|\*\.[^/:*]+)(?::(\d+|\*))?$/);
|
|
4
|
+
if (!match) return null;
|
|
5
|
+
const [, scheme, rawHost, port] = match;
|
|
6
|
+
const anySubdomain = rawHost.startsWith("*.");
|
|
7
|
+
return {
|
|
8
|
+
scheme,
|
|
9
|
+
host: anySubdomain ? rawHost.slice(2) : rawHost,
|
|
10
|
+
anySubdomain,
|
|
11
|
+
port: port ?? ""
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function isAllowedEditorOrigin(patterns, origin) {
|
|
15
|
+
let url;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(origin);
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const scheme = url.protocol.replace(/:$/, "");
|
|
22
|
+
const host = url.hostname.toLowerCase();
|
|
23
|
+
const port = url.port;
|
|
24
|
+
return patterns.some((pattern) => {
|
|
25
|
+
const p = parsePattern(pattern);
|
|
26
|
+
if (!p || p.scheme !== scheme) return false;
|
|
27
|
+
if (p.port !== "*" && p.port !== port) return false;
|
|
28
|
+
if (p.anySubdomain) return host !== p.host && host.endsWith("." + p.host);
|
|
29
|
+
return p.host === host;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function hasWildcardPattern(patterns) {
|
|
33
|
+
return patterns.some((p) => p.includes("*"));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/breakpoints.ts
|
|
37
|
+
var BREAKPOINTS = ["base", "tablet", "mobile"];
|
|
38
|
+
var STYLE_PROP_FOR_BREAKPOINT = {
|
|
39
|
+
base: "style",
|
|
40
|
+
tablet: "styleTablet",
|
|
41
|
+
mobile: "styleMobile"
|
|
42
|
+
};
|
|
43
|
+
var DEFAULT_CUTOFFS = { tablet: 1024, mobile: 640 };
|
|
44
|
+
function styleLayer(value) {
|
|
45
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function readNodeStyles(props) {
|
|
49
|
+
return {
|
|
50
|
+
base: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.base]),
|
|
51
|
+
tablet: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.tablet]),
|
|
52
|
+
mobile: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.mobile])
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function effectiveStyle(styles, breakpoint) {
|
|
56
|
+
const out = { ...styles.base ?? {} };
|
|
57
|
+
if (breakpoint !== "base") Object.assign(out, styles.tablet ?? {});
|
|
58
|
+
if (breakpoint === "mobile") Object.assign(out, styles.mobile ?? {});
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function resolveCutoffs(theme) {
|
|
62
|
+
const declared = theme?.breakpoints;
|
|
63
|
+
const pick = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
64
|
+
return {
|
|
65
|
+
tablet: pick(declared?.tablet, DEFAULT_CUTOFFS.tablet),
|
|
66
|
+
mobile: pick(declared?.mobile, DEFAULT_CUTOFFS.mobile)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function toCssName(key) {
|
|
70
|
+
if (key.startsWith("--")) return key;
|
|
71
|
+
return key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
72
|
+
}
|
|
73
|
+
var PROPERTY_NAME = /^(--[\w-]+|[a-z][a-z-]*)$/;
|
|
74
|
+
var FORBIDDEN_IN_VALUE = /[{}\\\u0000-\u001f\u007f]|\/\*|<\/style/i;
|
|
75
|
+
function sanitizeValue(raw) {
|
|
76
|
+
const value = raw.trim();
|
|
77
|
+
return value && !FORBIDDEN_IN_VALUE.test(value) ? value : null;
|
|
78
|
+
}
|
|
79
|
+
function declarations(layer) {
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const [key, raw] of Object.entries(layer)) {
|
|
82
|
+
if (typeof raw !== "string") continue;
|
|
83
|
+
const property = toCssName(key.trim());
|
|
84
|
+
if (!PROPERTY_NAME.test(property)) continue;
|
|
85
|
+
const value = sanitizeValue(raw);
|
|
86
|
+
if (value === null) continue;
|
|
87
|
+
out.push(`${property}: ${value} !important;`);
|
|
88
|
+
}
|
|
89
|
+
return out.join(" ");
|
|
90
|
+
}
|
|
91
|
+
function escapeAttr(id) {
|
|
92
|
+
return id.replace(/[\u0000-\u001f\u007f]/g, "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
93
|
+
}
|
|
94
|
+
function compileNodeCss(id, styles, cutoffs = DEFAULT_CUTOFFS) {
|
|
95
|
+
const selector = `[data-plenary-id="${escapeAttr(id)}"] > *`;
|
|
96
|
+
const parts = [];
|
|
97
|
+
const base = styles.base ? declarations(styles.base) : "";
|
|
98
|
+
if (base) parts.push(`${selector} { ${base} }`);
|
|
99
|
+
const tablet = styles.tablet ? declarations(styles.tablet) : "";
|
|
100
|
+
if (tablet) parts.push(`@media (max-width: ${cutoffs.tablet}px) { ${selector} { ${tablet} } }`);
|
|
101
|
+
const mobile = styles.mobile ? declarations(styles.mobile) : "";
|
|
102
|
+
if (mobile) parts.push(`@media (max-width: ${cutoffs.mobile}px) { ${selector} { ${mobile} } }`);
|
|
103
|
+
return parts.join("\n");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/protocol.ts
|
|
107
|
+
var PROTOCOL_VERSION = 1;
|
|
108
|
+
var DND_NEW = "application/x-plenary-new";
|
|
109
|
+
export {
|
|
110
|
+
BREAKPOINTS,
|
|
111
|
+
DEFAULT_CUTOFFS,
|
|
112
|
+
DND_NEW,
|
|
113
|
+
PROTOCOL_VERSION,
|
|
114
|
+
STYLE_PROP_FOR_BREAKPOINT,
|
|
115
|
+
compileNodeCss,
|
|
116
|
+
effectiveStyle,
|
|
117
|
+
hasWildcardPattern,
|
|
118
|
+
isAllowedEditorOrigin,
|
|
119
|
+
readNodeStyles,
|
|
120
|
+
resolveCutoffs
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/origins.ts","../src/breakpoints.ts","../src/protocol.ts"],"sourcesContent":["/**\n * Editor-origin matching — which console origins a site's preview bridge will\n * speak to. A site lists patterns; every inbound bridge message must come from\n * an origin matching one of them.\n *\n * Pattern forms:\n *\n * - Exact origin: `https://cms.example.com`, `http://localhost:5173`\n * - Subdomain wildcard: `https://*.example.com` — matches any depth of\n * subdomain (`a.example.com`, `a.b.example.com`) but never the bare apex.\n * `*.` is only valid as the leftmost host label.\n * - Port wildcard: `http://localhost:*` — matches any port, including none.\n *\n * The scheme is always literal (no wildcard), and a bare `*` host is not\n * supported: allowing any origin would let any page that frames the site drive\n * its preview. List the origins you trust, wildcarded no wider than a domain\n * you control.\n */\n\n/** One parsed pattern; null when the pattern is malformed (matches nothing). */\ninterface ParsedPattern {\n scheme: string;\n /** Host to compare against; when `anySubdomain`, the apex the host must be under. */\n host: string;\n anySubdomain: boolean;\n /** Explicit port, \"\" for scheme-default, or \"*\" for any. */\n port: string;\n}\n\nfunction parsePattern(pattern: string): ParsedPattern | null {\n const match = pattern\n .trim()\n .toLowerCase()\n .match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/:*]+|\\*\\.[^/:*]+)(?::(\\d+|\\*))?$/);\n if (!match) return null;\n const [, scheme, rawHost, port] = match;\n const anySubdomain = rawHost!.startsWith(\"*.\");\n return {\n scheme: scheme!,\n host: anySubdomain ? rawHost!.slice(2) : rawHost!,\n anySubdomain,\n port: port ?? \"\",\n };\n}\n\n/**\n * True when `origin` (a browser origin string, e.g. a MessageEvent's `origin`)\n * is allowed by any of `patterns`. Malformed patterns and origins match nothing.\n *\n * Browsers normalize origins to omit scheme-default ports, so a pattern without\n * a port matches exactly the default-port origin, and an explicit port must\n * match explicitly.\n */\nexport function isAllowedEditorOrigin(patterns: readonly string[], origin: string): boolean {\n let url: URL;\n try {\n url = new URL(origin);\n } catch {\n return false;\n }\n const scheme = url.protocol.replace(/:$/, \"\");\n const host = url.hostname.toLowerCase();\n const port = url.port;\n\n return patterns.some((pattern) => {\n const p = parsePattern(pattern);\n if (!p || p.scheme !== scheme) return false;\n if (p.port !== \"*\" && p.port !== port) return false;\n if (p.anySubdomain) return host !== p.host && host.endsWith(\".\" + p.host);\n return p.host === host;\n });\n}\n\n/** True when any pattern uses a wildcard (and so cannot be a postMessage target). */\nexport function hasWildcardPattern(patterns: readonly string[]): boolean {\n return patterns.some((p) => p.includes(\"*\"));\n}\n","/**\n * The responsive style model: three style layers per node, desktop-first.\n *\n * A node's editor styles live on its props as flat camelCase records —\n * `style` (base), `styleTablet`, `styleMobile`. `tablet` overrides `base` at\n * widths ≤ the tablet cutoff, `mobile` overrides both at ≤ the mobile cutoff;\n * overrides are per-property, not per-object.\n *\n * The compiler turns those layers into per-node CSS targeting the renderer's\n * annotation wrapper (`[data-plenary-id=\"…\"] > *`). Every declaration is\n * emitted `!important`: editor styles were historically inline (which beats\n * all non-important site CSS), and the generated rules preserve exactly that\n * precedence while still cascading correctly among themselves — the three\n * rules share one specificity, so the later media block wins where it applies.\n */\n\nimport type { ThemeRegistration } from \"./registration.js\";\n\nexport type Breakpoint = \"base\" | \"tablet\" | \"mobile\";\n\nexport const BREAKPOINTS: readonly Breakpoint[] = [\"base\", \"tablet\", \"mobile\"];\n\n/** The node prop each breakpoint's style layer is stored in. */\nexport const STYLE_PROP_FOR_BREAKPOINT: Record<Breakpoint, string> = {\n base: \"style\",\n tablet: \"styleTablet\",\n mobile: \"styleMobile\",\n};\n\n/** The `max-width` cutoffs (px) at which the override layers apply. */\nexport interface BreakpointCutoffs {\n tablet: number;\n mobile: number;\n}\n\nexport const DEFAULT_CUTOFFS: BreakpointCutoffs = { tablet: 1024, mobile: 640 };\n\n/** A node's three style layers; absent layer = no styles there. */\nexport interface NodeStyles {\n base?: Record<string, string>;\n tablet?: Record<string, string>;\n mobile?: Record<string, string>;\n}\n\n/** One flat camelCase style record, defensively narrowed from a prop value. */\nfunction styleLayer(value: unknown): Record<string, string> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n return value as Record<string, string>;\n}\n\n/** Read a node's style layers off its props. */\nexport function readNodeStyles(props: Record<string, unknown>): NodeStyles {\n return {\n base: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.base]),\n tablet: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.tablet]),\n mobile: styleLayer(props[STYLE_PROP_FOR_BREAKPOINT.mobile]),\n };\n}\n\n/**\n * The effective (cascaded) style at a breakpoint: base, overlaid per-property\n * by tablet (at tablet and below), then mobile (at mobile only).\n */\nexport function effectiveStyle(\n styles: NodeStyles,\n breakpoint: Breakpoint,\n): Record<string, string> {\n const out: Record<string, string> = { ...(styles.base ?? {}) };\n if (breakpoint !== \"base\") Object.assign(out, styles.tablet ?? {});\n if (breakpoint === \"mobile\") Object.assign(out, styles.mobile ?? {});\n return out;\n}\n\n/**\n * The cutoffs a site's theme declares, defaulted and validated — a cutoff must\n * be a positive finite number of px or it falls back to the default.\n */\nexport function resolveCutoffs(theme: ThemeRegistration | undefined): BreakpointCutoffs {\n const declared = theme?.breakpoints;\n const pick = (value: number | undefined, fallback: number) =>\n typeof value === \"number\" && Number.isFinite(value) && value > 0\n ? Math.floor(value)\n : fallback;\n return {\n tablet: pick(declared?.tablet, DEFAULT_CUTOFFS.tablet),\n mobile: pick(declared?.mobile, DEFAULT_CUTOFFS.mobile),\n };\n}\n\n// --- compilation -----------------------------------------------------------\n\n/** camelCase style key → kebab-case CSS property. Custom properties pass through. */\nfunction toCssName(key: string): string {\n if (key.startsWith(\"--\")) return key;\n return key.replace(/[A-Z]/g, (m) => \"-\" + m.toLowerCase());\n}\n\n/** A legal property name after kebab conversion: `color`, `font-size`, `--x`. */\nconst PROPERTY_NAME = /^(--[\\w-]+|[a-z][a-z-]*)$/;\n\n/**\n * Sequences a value may never contain: rule/selector escapes (braces,\n * backslash), comment openers, a premature `</style`, and control characters.\n * A value carrying any of them is dropped whole — no repair attempts, since a\n * \"repaired\" value is a value the user never wrote. `;` is allowed: it can at\n * worst add a declaration to the node's own rule, which the CSS editor permits\n * anyway, and data URIs need it.\n */\nconst FORBIDDEN_IN_VALUE = /[{}\\\\\\u0000-\\u001f\\u007f]|\\/\\*|<\\/style/i;\n\nfunction sanitizeValue(raw: string): string | null {\n const value = raw.trim();\n return value && !FORBIDDEN_IN_VALUE.test(value) ? value : null;\n}\n\n/** One layer's declarations as CSS text; \"\" when nothing survives. */\nfunction declarations(layer: Record<string, string>): string {\n const out: string[] = [];\n for (const [key, raw] of Object.entries(layer)) {\n if (typeof raw !== \"string\") continue;\n const property = toCssName(key.trim());\n if (!PROPERTY_NAME.test(property)) continue;\n const value = sanitizeValue(raw);\n if (value === null) continue;\n out.push(`${property}: ${value} !important;`);\n }\n return out.join(\" \");\n}\n\n/** The node id, escaped for use inside a double-quoted attribute selector. */\nfunction escapeAttr(id: string): string {\n return id.replace(/[\\u0000-\\u001f\\u007f]/g, \"\").replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Compile one node's style layers to CSS: a base rule plus a `@media\n * (max-width)` block per override layer, in cascade order. Returns \"\" when no\n * declaration survives — callers skip the node entirely.\n */\nexport function compileNodeCss(\n id: string,\n styles: NodeStyles,\n cutoffs: BreakpointCutoffs = DEFAULT_CUTOFFS,\n): string {\n const selector = `[data-plenary-id=\"${escapeAttr(id)}\"] > *`;\n const parts: string[] = [];\n const base = styles.base ? declarations(styles.base) : \"\";\n if (base) parts.push(`${selector} { ${base} }`);\n const tablet = styles.tablet ? declarations(styles.tablet) : \"\";\n if (tablet) parts.push(`@media (max-width: ${cutoffs.tablet}px) { ${selector} { ${tablet} } }`);\n const mobile = styles.mobile ? declarations(styles.mobile) : \"\";\n if (mobile) parts.push(`@media (max-width: ${cutoffs.mobile}px) { ${selector} { ${mobile} } }`);\n return parts.join(\"\\n\");\n}\n","/**\n * The postMessage protocol — the contract between a site's preview bridge\n * (`@plenary/react`) and the console's page builder. These types are the single\n * source of truth every framework adapter and the console share.\n *\n * Origin discipline is the security model: the bridge ignores any message whose\n * origin is not in its configured `editorOrigins`, and the console ignores any\n * message not from the iframe's known origin.\n */\n\nimport type {\n PropDefinition,\n SlotDefinition,\n StylePanel,\n ThemeRegistration,\n} from \"./registration.js\";\n\n/** Current protocol version; negotiated in the handshake. Bumped on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Drag data-transfer key for a new component dragged from the console palette.\n * The bridge reads it on `drop` over the live preview to know which component to\n * insert. Kept in sync with the console's own constant.\n */\nexport const DND_NEW = \"application/x-plenary-new\";\n\n/** Every message on the wire is wrapped in this envelope. */\nexport interface PlenaryMessage<TType extends string, TPayload> {\n /** Discriminates Plenary traffic from other postMessage senders. */\n plenary: true;\n /** Protocol version of the sender. */\n v: number;\n type: TType;\n payload: TPayload;\n}\n\n/** A component as announced in the manifest (normalized from its registration). */\nexport interface ManifestComponent {\n /** Registration key used in the DOM (\"hero\"). */\n name: string;\n label: string;\n icon?: string;\n props: PropDefinition[];\n slots: SlotDefinition[];\n category?: string;\n /** Style panels the component exposes; absent means none. */\n styles?: StylePanel[];\n}\n\n// --- Payloads -------------------------------------------------------------\n\n/** `hello` — bridge announces it is ready. */\nexport interface HelloPayload {\n protocolVersion: number;\n sdkVersion: string;\n}\n\n/** `init` — console acks the handshake and supplies context. */\nexport interface InitPayload {\n entryId: string;\n modelKey: string;\n editorOrigin: string;\n}\n\n/** `manifest` — the full component catalog (built-ins + custom) and theme. */\nexport interface ManifestPayload {\n components: ManifestComponent[];\n /** The site's design tokens, when it registered any. */\n theme?: ThemeRegistration;\n}\n\n/**\n * `render` — the draft entry the bridge should render instead of the API's copy.\n * A component tree lives in `data` as a `blocks` field; the bridge renders the\n * field named by its `region`.\n */\nexport interface RenderPayload {\n data: Record<string, unknown>;\n locale: string;\n}\n\n/** `patch` — a targeted prop update (optimization; v1 may just re-send `render`). */\nexport interface PatchPayload {\n nodeId: string;\n props: Record<string, unknown>;\n}\n\n/** `select` — user clicked a node in the preview. */\nexport interface SelectPayload {\n nodeId: string;\n}\n\n/** `hover` — pointer moved over a node (or off all nodes). */\nexport interface HoverPayload {\n nodeId: string | null;\n}\n\n/**\n * `highlight` — console asks the preview to outline nodes from the sidebar.\n * Two independent channels, drawn with distinct styles: `selectedId` is the\n * block being edited (strong outline), `hoverId` is whatever the cursor is over\n * — a tree row or a preview element — (lighter outline). Either may be null.\n */\nexport interface HighlightPayload {\n selectedId: string | null;\n hoverId: string | null;\n}\n\n/** Where a component dropped on the live canvas should land, in tree terms. */\nexport type CanvasDropTarget =\n | { at: \"root\" }\n | { at: \"before\" | \"after\"; nodeId: string }\n | { at: \"slot\"; nodeId: string; slot: string };\n\n/** `canvasDrop` — a palette component was dropped onto the rendered preview. */\nexport interface CanvasDropPayload {\n component: string;\n target: CanvasDropTarget;\n}\n\n/**\n * `size` — the page's full content height in CSS px, reported by the bridge on\n * load and whenever it changes. The console's artboard sizes the preview iframe\n * to it so the whole page is visible with no inner scrollbar.\n */\nexport interface SizePayload {\n height: number;\n}\n\n/**\n * `wheel` — a Ctrl/Cmd+wheel zoom gesture performed over the page. The iframe\n * consumes wheel events, so the bridge forwards them for the console's artboard\n * to zoom; `x`/`y` are the cursor in page (iframe-viewport) coordinates, which\n * are the artboard's unscaled frame coordinates.\n */\nexport interface WheelPayload {\n deltaY: number;\n x: number;\n y: number;\n}\n\n/**\n * `pan` — a middle-button drag step over the page (the hand tool). `dx`/`dy`\n * are the pointer's movement since the last step, in page coordinates.\n */\nexport interface PanPayload {\n dx: number;\n dy: number;\n}\n\n/**\n * `key` — an editor keyboard shortcut pressed while focus is inside the frame\n * (clicking a block focuses the iframe, so the console's own listener never\n * sees these). The bridge forwards only recognized shortcut combinations and\n * prevents their default in the frame; the console dispatches them exactly as\n * if they had been pressed on its window.\n */\nexport interface KeyPayload {\n key: string;\n ctrlKey: boolean;\n metaKey: boolean;\n shiftKey: boolean;\n}\n\n/**\n * `edit` — an in-place edit committed inside the frame (double-click inline\n * text editing). The bridge has already applied it to its local draft; the\n * console applies it to the real draft as one undoable step and its next\n * `render` echo matches what the frame is showing.\n */\nexport interface EditPayload {\n nodeId: string;\n props: Record<string, unknown>;\n}\n\n// --- Messages -------------------------------------------------------------\n\nexport type HelloMessage = PlenaryMessage<\"hello\", HelloPayload>;\nexport type InitMessage = PlenaryMessage<\"init\", InitPayload>;\nexport type ManifestMessage = PlenaryMessage<\"manifest\", ManifestPayload>;\nexport type RenderMessage = PlenaryMessage<\"render\", RenderPayload>;\nexport type PatchMessage = PlenaryMessage<\"patch\", PatchPayload>;\nexport type SelectMessage = PlenaryMessage<\"select\", SelectPayload>;\nexport type HoverMessage = PlenaryMessage<\"hover\", HoverPayload>;\nexport type HighlightMessage = PlenaryMessage<\"highlight\", HighlightPayload>;\nexport type CanvasDropMessage = PlenaryMessage<\"canvasDrop\", CanvasDropPayload>;\nexport type SizeMessage = PlenaryMessage<\"size\", SizePayload>;\nexport type WheelMessage = PlenaryMessage<\"wheel\", WheelPayload>;\nexport type PanMessage = PlenaryMessage<\"pan\", PanPayload>;\nexport type KeyMessage = PlenaryMessage<\"key\", KeyPayload>;\nexport type EditMessage = PlenaryMessage<\"edit\", EditPayload>;\n\n/** Messages the bridge (iframe) sends up to the console. */\nexport type IframeToConsoleMessage =\n | HelloMessage\n | ManifestMessage\n | SelectMessage\n | HoverMessage\n | CanvasDropMessage\n | SizeMessage\n | WheelMessage\n | PanMessage\n | KeyMessage\n | EditMessage;\n\n/** Messages the console sends down to the bridge (iframe). */\nexport type ConsoleToIframeMessage =\n | InitMessage\n | RenderMessage\n | PatchMessage\n | HighlightMessage;\n\n/** Any message in the protocol, either direction. */\nexport type PlenaryProtocolMessage = IframeToConsoleMessage | ConsoleToIframeMessage;\n\n/** Maps each message `type` to its payload — useful for typed senders/handlers. */\nexport interface PlenaryMessagePayloads {\n hello: HelloPayload;\n init: InitPayload;\n manifest: ManifestPayload;\n render: RenderPayload;\n patch: PatchPayload;\n select: SelectPayload;\n hover: HoverPayload;\n highlight: HighlightPayload;\n canvasDrop: CanvasDropPayload;\n size: SizePayload;\n wheel: WheelPayload;\n pan: PanPayload;\n key: KeyPayload;\n edit: EditPayload;\n}\n\n/** Every protocol message `type` string. */\nexport type PlenaryMessageType = keyof PlenaryMessagePayloads;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plenary/core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Plenary SDK core types: DOM nodes, component registration, prop definitions, and the preview protocol. Types only, zero deps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
}
|
|
27
|
+
}
|