@plenary/core 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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":[]}
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 CanvasDragPayload,\n type CanvasDragDropPayload,\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 CanvasDragOverMessage,\n type CanvasDragLeaveMessage,\n type CanvasDragDropMessage,\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 * `canvasDragOver` / `canvasDragDrop` — where a palette drag currently is over\n * the rendered preview, in the framed page's own CSS pixel space (the console\n * has already undone the artboard's zoom).\n *\n * The console reports this rather than the page reading the drag itself: the\n * browser delivers no drag events over a cross-origin iframe — not to the frame,\n * and not to the page around it — so a preview on the customer's own origin\n * never sees the drag at all. The console catches it over its own artboard and\n * forwards the position; the bridge resolves it against the rendered DOM, which\n * only it can see, and answers `canvasDrop`.\n */\nexport interface CanvasDragPayload {\n x: number;\n y: number;\n}\n\n/** `canvasDragDrop` — the drag was released; carries what to insert. */\nexport interface CanvasDragDropPayload extends CanvasDragPayload {\n component: string;\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 CanvasDragOverMessage = PlenaryMessage<\"canvasDragOver\", CanvasDragPayload>;\nexport type CanvasDragLeaveMessage = PlenaryMessage<\"canvasDragLeave\", Record<string, never>>;\nexport type CanvasDragDropMessage = PlenaryMessage<\"canvasDragDrop\", CanvasDragDropPayload>;\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 | CanvasDragOverMessage\n | CanvasDragLeaveMessage\n | CanvasDragDropMessage;\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 canvasDragOver: CanvasDragPayload;\n canvasDragLeave: Record<string, never>;\n canvasDragDrop: CanvasDragDropPayload;\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 CHANGED
@@ -320,6 +320,26 @@ interface CanvasDropPayload {
320
320
  component: string;
321
321
  target: CanvasDropTarget;
322
322
  }
323
+ /**
324
+ * `canvasDragOver` / `canvasDragDrop` — where a palette drag currently is over
325
+ * the rendered preview, in the framed page's own CSS pixel space (the console
326
+ * has already undone the artboard's zoom).
327
+ *
328
+ * The console reports this rather than the page reading the drag itself: the
329
+ * browser delivers no drag events over a cross-origin iframe — not to the frame,
330
+ * and not to the page around it — so a preview on the customer's own origin
331
+ * never sees the drag at all. The console catches it over its own artboard and
332
+ * forwards the position; the bridge resolves it against the rendered DOM, which
333
+ * only it can see, and answers `canvasDrop`.
334
+ */
335
+ interface CanvasDragPayload {
336
+ x: number;
337
+ y: number;
338
+ }
339
+ /** `canvasDragDrop` — the drag was released; carries what to insert. */
340
+ interface CanvasDragDropPayload extends CanvasDragPayload {
341
+ component: string;
342
+ }
323
343
  /**
324
344
  * `size` — the page's full content height in CSS px, reported by the bridge on
325
345
  * load and whenever it changes. The console's artboard sizes the preview iframe
@@ -379,6 +399,9 @@ type SelectMessage = PlenaryMessage<"select", SelectPayload>;
379
399
  type HoverMessage = PlenaryMessage<"hover", HoverPayload>;
380
400
  type HighlightMessage = PlenaryMessage<"highlight", HighlightPayload>;
381
401
  type CanvasDropMessage = PlenaryMessage<"canvasDrop", CanvasDropPayload>;
402
+ type CanvasDragOverMessage = PlenaryMessage<"canvasDragOver", CanvasDragPayload>;
403
+ type CanvasDragLeaveMessage = PlenaryMessage<"canvasDragLeave", Record<string, never>>;
404
+ type CanvasDragDropMessage = PlenaryMessage<"canvasDragDrop", CanvasDragDropPayload>;
382
405
  type SizeMessage = PlenaryMessage<"size", SizePayload>;
383
406
  type WheelMessage = PlenaryMessage<"wheel", WheelPayload>;
384
407
  type PanMessage = PlenaryMessage<"pan", PanPayload>;
@@ -387,7 +410,7 @@ type EditMessage = PlenaryMessage<"edit", EditPayload>;
387
410
  /** Messages the bridge (iframe) sends up to the console. */
388
411
  type IframeToConsoleMessage = HelloMessage | ManifestMessage | SelectMessage | HoverMessage | CanvasDropMessage | SizeMessage | WheelMessage | PanMessage | KeyMessage | EditMessage;
389
412
  /** Messages the console sends down to the bridge (iframe). */
390
- type ConsoleToIframeMessage = InitMessage | RenderMessage | PatchMessage | HighlightMessage;
413
+ type ConsoleToIframeMessage = InitMessage | RenderMessage | PatchMessage | HighlightMessage | CanvasDragOverMessage | CanvasDragLeaveMessage | CanvasDragDropMessage;
391
414
  /** Any message in the protocol, either direction. */
392
415
  type PlenaryProtocolMessage = IframeToConsoleMessage | ConsoleToIframeMessage;
393
416
  /** Maps each message `type` to its payload — useful for typed senders/handlers. */
@@ -401,6 +424,9 @@ interface PlenaryMessagePayloads {
401
424
  hover: HoverPayload;
402
425
  highlight: HighlightPayload;
403
426
  canvasDrop: CanvasDropPayload;
427
+ canvasDragOver: CanvasDragPayload;
428
+ canvasDragLeave: Record<string, never>;
429
+ canvasDragDrop: CanvasDragDropPayload;
404
430
  size: SizePayload;
405
431
  wheel: WheelPayload;
406
432
  pan: PanPayload;
@@ -410,4 +436,4 @@ interface PlenaryMessagePayloads {
410
436
  /** Every protocol message `type` string. */
411
437
  type PlenaryMessageType = keyof PlenaryMessagePayloads;
412
438
 
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 };
439
+ export { BREAKPOINTS, type Breakpoint, type BreakpointCutoffs, type CanvasDragDropMessage, type CanvasDragDropPayload, type CanvasDragLeaveMessage, type CanvasDragOverMessage, type CanvasDragPayload, 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 CHANGED
@@ -320,6 +320,26 @@ interface CanvasDropPayload {
320
320
  component: string;
321
321
  target: CanvasDropTarget;
322
322
  }
323
+ /**
324
+ * `canvasDragOver` / `canvasDragDrop` — where a palette drag currently is over
325
+ * the rendered preview, in the framed page's own CSS pixel space (the console
326
+ * has already undone the artboard's zoom).
327
+ *
328
+ * The console reports this rather than the page reading the drag itself: the
329
+ * browser delivers no drag events over a cross-origin iframe — not to the frame,
330
+ * and not to the page around it — so a preview on the customer's own origin
331
+ * never sees the drag at all. The console catches it over its own artboard and
332
+ * forwards the position; the bridge resolves it against the rendered DOM, which
333
+ * only it can see, and answers `canvasDrop`.
334
+ */
335
+ interface CanvasDragPayload {
336
+ x: number;
337
+ y: number;
338
+ }
339
+ /** `canvasDragDrop` — the drag was released; carries what to insert. */
340
+ interface CanvasDragDropPayload extends CanvasDragPayload {
341
+ component: string;
342
+ }
323
343
  /**
324
344
  * `size` — the page's full content height in CSS px, reported by the bridge on
325
345
  * load and whenever it changes. The console's artboard sizes the preview iframe
@@ -379,6 +399,9 @@ type SelectMessage = PlenaryMessage<"select", SelectPayload>;
379
399
  type HoverMessage = PlenaryMessage<"hover", HoverPayload>;
380
400
  type HighlightMessage = PlenaryMessage<"highlight", HighlightPayload>;
381
401
  type CanvasDropMessage = PlenaryMessage<"canvasDrop", CanvasDropPayload>;
402
+ type CanvasDragOverMessage = PlenaryMessage<"canvasDragOver", CanvasDragPayload>;
403
+ type CanvasDragLeaveMessage = PlenaryMessage<"canvasDragLeave", Record<string, never>>;
404
+ type CanvasDragDropMessage = PlenaryMessage<"canvasDragDrop", CanvasDragDropPayload>;
382
405
  type SizeMessage = PlenaryMessage<"size", SizePayload>;
383
406
  type WheelMessage = PlenaryMessage<"wheel", WheelPayload>;
384
407
  type PanMessage = PlenaryMessage<"pan", PanPayload>;
@@ -387,7 +410,7 @@ type EditMessage = PlenaryMessage<"edit", EditPayload>;
387
410
  /** Messages the bridge (iframe) sends up to the console. */
388
411
  type IframeToConsoleMessage = HelloMessage | ManifestMessage | SelectMessage | HoverMessage | CanvasDropMessage | SizeMessage | WheelMessage | PanMessage | KeyMessage | EditMessage;
389
412
  /** Messages the console sends down to the bridge (iframe). */
390
- type ConsoleToIframeMessage = InitMessage | RenderMessage | PatchMessage | HighlightMessage;
413
+ type ConsoleToIframeMessage = InitMessage | RenderMessage | PatchMessage | HighlightMessage | CanvasDragOverMessage | CanvasDragLeaveMessage | CanvasDragDropMessage;
391
414
  /** Any message in the protocol, either direction. */
392
415
  type PlenaryProtocolMessage = IframeToConsoleMessage | ConsoleToIframeMessage;
393
416
  /** Maps each message `type` to its payload — useful for typed senders/handlers. */
@@ -401,6 +424,9 @@ interface PlenaryMessagePayloads {
401
424
  hover: HoverPayload;
402
425
  highlight: HighlightPayload;
403
426
  canvasDrop: CanvasDropPayload;
427
+ canvasDragOver: CanvasDragPayload;
428
+ canvasDragLeave: Record<string, never>;
429
+ canvasDragDrop: CanvasDragDropPayload;
404
430
  size: SizePayload;
405
431
  wheel: WheelPayload;
406
432
  pan: PanPayload;
@@ -410,4 +436,4 @@ interface PlenaryMessagePayloads {
410
436
  /** Every protocol message `type` string. */
411
437
  type PlenaryMessageType = keyof PlenaryMessagePayloads;
412
438
 
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 };
439
+ export { BREAKPOINTS, type Breakpoint, type BreakpointCutoffs, type CanvasDragDropMessage, type CanvasDragDropPayload, type CanvasDragLeaveMessage, type CanvasDragOverMessage, type CanvasDragPayload, 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.map CHANGED
@@ -1 +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":[]}
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 * `canvasDragOver` / `canvasDragDrop` — where a palette drag currently is over\n * the rendered preview, in the framed page's own CSS pixel space (the console\n * has already undone the artboard's zoom).\n *\n * The console reports this rather than the page reading the drag itself: the\n * browser delivers no drag events over a cross-origin iframe — not to the frame,\n * and not to the page around it — so a preview on the customer's own origin\n * never sees the drag at all. The console catches it over its own artboard and\n * forwards the position; the bridge resolves it against the rendered DOM, which\n * only it can see, and answers `canvasDrop`.\n */\nexport interface CanvasDragPayload {\n x: number;\n y: number;\n}\n\n/** `canvasDragDrop` — the drag was released; carries what to insert. */\nexport interface CanvasDragDropPayload extends CanvasDragPayload {\n component: string;\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 CanvasDragOverMessage = PlenaryMessage<\"canvasDragOver\", CanvasDragPayload>;\nexport type CanvasDragLeaveMessage = PlenaryMessage<\"canvasDragLeave\", Record<string, never>>;\nexport type CanvasDragDropMessage = PlenaryMessage<\"canvasDragDrop\", CanvasDragDropPayload>;\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 | CanvasDragOverMessage\n | CanvasDragLeaveMessage\n | CanvasDragDropMessage;\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 canvasDragOver: CanvasDragPayload;\n canvasDragLeave: Record<string, never>;\n canvasDragDrop: CanvasDragDropPayload;\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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plenary/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Plenary SDK core types: DOM nodes, component registration, prop definitions, and the preview protocol. Types only, zero deps.",
5
5
  "type": "module",
6
6
  "sideEffects": false,