@distinctagency/cms-client 1.23.0 → 1.25.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/visual-editing/react.tsx","../src/visual-editing/protocol.ts","../src/visual-editing/dom.ts","../src/visual-editing/bridge.ts"],"sourcesContent":["// packages/client/src/visual-editing/react.tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { createVisualBridge } from \"./bridge\"\n\nexport interface CmsVisualBridgeProps {\n /** Override the CMS origins the bridge trusts (defaults to Distinct CMS origins + localhost). */\n trustedOrigins?: string[]\n /** Query param that activates preview mode. Defaults to \"cms_preview\". */\n param?: string\n}\n\n/**\n * Mount once in your site (e.g. root layout). Renders nothing and stays inert\n * for normal visitors — it only activates when the page is loaded with the\n * preview param inside a trusted CMS frame.\n */\nexport function CmsVisualBridge({ trustedOrigins, param = \"cms_preview\" }: CmsVisualBridgeProps) {\n useEffect(() => {\n if (typeof window === \"undefined\") return\n const params = new URLSearchParams(window.location.search)\n if (!params.has(param)) return\n const cleanup = createVisualBridge({ trustedOrigins })\n return cleanup\n }, [trustedOrigins, param])\n return null\n}\n","/** Discriminator stamped on every visual-editing postMessage. */\nexport const CMS_VISUAL_SOURCE = \"cms-visual\" as const\n\n/** CMS origins the bridge will trust by default. */\nexport const DEFAULT_TRUSTED_ORIGINS: readonly string[] = [\n \"https://cms.distinctstudio.co.nz\",\n \"https://distinctcms.com\",\n]\n\nexport type VisualFieldType = \"text\" | \"image\"\n\n/** Compact wire type announced in `ready`; the schema adds length constraints. */\nexport interface VisualFieldMeta {\n name: string\n type: VisualFieldType\n}\n\nexport interface VisualFieldSchema {\n name: string\n type: VisualFieldType\n max_length?: number\n recommended_length?: number\n}\n\n// bridge -> parent\nexport interface ReadyMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"ready\"\n fields: VisualFieldMeta[]\n}\nexport interface EditMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"edit\"\n name: string\n value: string\n length: number\n}\nexport interface PickImageMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"pick-image\"\n name: string\n}\n\n// parent -> bridge\nexport interface InitMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"init\"\n values: Record<string, string>\n schema: Record<string, VisualFieldSchema>\n}\nexport interface SetMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"set\"\n name: string\n value: string\n}\n\nexport type BridgeOutbound = ReadyMessage | EditMessage | PickImageMessage\nexport type BridgeInbound = InitMessage | SetMessage\nexport type VisualMessage = BridgeOutbound | BridgeInbound\n\n/** Exact-match origin allowlist, plus any localhost port for local dev. */\nexport function isTrustedOrigin(\n origin: string,\n allowlist: readonly string[]\n): boolean {\n if (allowlist.includes(origin)) return true\n return /^https?:\\/\\/localhost(:\\d+)?$/.test(origin)\n}\n\nconst VALID_VISUAL_TYPES = new Set([\"ready\", \"edit\", \"pick-image\", \"init\", \"set\"])\n\n/** Narrow an unknown postMessage payload to a visual message, or null. */\nexport function parseVisualMessage(data: unknown): VisualMessage | null {\n if (\n typeof data === \"object\" &&\n data !== null &&\n (data as Record<string, unknown>).source === CMS_VISUAL_SOURCE &&\n typeof (data as Record<string, unknown>).type === \"string\" &&\n VALID_VISUAL_TYPES.has((data as Record<string, unknown>).type as string)\n ) {\n return data as VisualMessage\n }\n return null\n}\n","import type { VisualFieldMeta } from \"./protocol\"\n\n/** Read every [data-cms-field] element in document order. */\nexport function discoverFields(root: Document | HTMLElement): VisualFieldMeta[] {\n const nodes = root.querySelectorAll<HTMLElement>(\"[data-cms-field]\")\n const fields: VisualFieldMeta[] = []\n nodes.forEach((el) => {\n const name = el.getAttribute(\"data-cms-field\")\n if (!name) return\n const type = el.getAttribute(\"data-cms-type\") === \"image\" ? \"image\" : \"text\"\n fields.push({ name, type })\n })\n return fields\n}\n\n/** Truncate text to a hard max. Undefined max means no limit. */\nexport function enforceMaxLength(value: string, max: number | undefined): string {\n if (max == null) return value\n return value.length > max ? value.slice(0, Math.max(0, max)) : value\n}\n","// packages/client/src/visual-editing/bridge.ts\nimport {\n CMS_VISUAL_SOURCE,\n DEFAULT_TRUSTED_ORIGINS,\n isTrustedOrigin,\n parseVisualMessage,\n type BridgeOutbound,\n type VisualFieldSchema,\n} from \"./protocol\"\nimport { discoverFields, enforceMaxLength } from \"./dom\"\n\nexport interface VisualBridgeOptions {\n trustedOrigins?: string[]\n}\n\n/**\n * Mounts the in-page editor bridge. Dormant until a trusted CMS parent sends\n * `init`. Returns a cleanup function. No-op when not running inside a frame.\n */\nexport function createVisualBridge(options: VisualBridgeOptions = {}): () => void {\n if (typeof window === \"undefined\" || window.parent === window) return () => {}\n const allow = options.trustedOrigins ?? DEFAULT_TRUSTED_ORIGINS\n\n let parentOrigin: string | null = null\n let schema: Record<string, VisualFieldSchema> = {}\n let activeEl: HTMLElement | null = null\n const counter = createCounterEl()\n\n function post(msg: BridgeOutbound) {\n if (parentOrigin) window.parent.postMessage(msg, parentOrigin)\n }\n\n function elFor(name: string): HTMLElement | null {\n return document.querySelector<HTMLElement>(`[data-cms-field=\"${CSS.escape(name)}\"]`)\n }\n\n function applyValue(name: string, value: string) {\n const el = elFor(name)\n if (!el) return\n if (el.getAttribute(\"data-cms-type\") === \"image\") {\n if (el instanceof HTMLImageElement) el.src = value\n } else {\n el.textContent = value\n }\n }\n\n function onClick(e: MouseEvent) {\n const el = (e.target as HTMLElement)?.closest<HTMLElement>(\"[data-cms-field]\")\n if (!el || !parentOrigin) return\n const name = el.getAttribute(\"data-cms-field\")!\n e.preventDefault()\n if (el.getAttribute(\"data-cms-type\") === \"image\") {\n post({ source: CMS_VISUAL_SOURCE, type: \"pick-image\", name })\n return\n }\n // text: edit in place\n activeEl = el\n el.setAttribute(\"contenteditable\", \"true\")\n el.focus()\n positionCounter(el, name)\n }\n\n function onInput(e: Event) {\n const el = e.target as HTMLElement\n const name = el.getAttribute?.(\"data-cms-field\")\n if (!name || el !== activeEl) return\n const max = schema[name]?.max_length\n const clamped = enforceMaxLength(el.textContent ?? \"\", max)\n if (clamped !== el.textContent) {\n el.textContent = clamped\n placeCaretAtEnd(el)\n }\n updateCounter(name, clamped.length)\n post({ source: CMS_VISUAL_SOURCE, type: \"edit\", name, value: clamped, length: clamped.length })\n }\n\n function onBlur(e: FocusEvent) {\n const el = e.target as HTMLElement\n if (el === activeEl) {\n el.removeAttribute(\"contenteditable\")\n activeEl = null\n counter.style.display = \"none\"\n }\n }\n\n function positionCounter(el: HTMLElement, name: string) {\n const r = el.getBoundingClientRect()\n counter.style.top = `${window.scrollY + r.bottom + 4}px`\n counter.style.left = `${window.scrollX + r.left}px`\n counter.style.display = \"block\"\n updateCounter(name, (el.textContent ?? \"\").length)\n }\n\n function updateCounter(name: string, len: number) {\n const f = schema[name]\n const rec = f?.recommended_length\n const max = f?.max_length\n counter.textContent = max ? `${len} / ${rec ?? max}${rec && max ? ` · max ${max}` : \"\"}` : `${len}`\n const over = max != null && len >= max\n const warn = !over && rec != null && len > rec\n counter.style.background = over ? \"#ef4444\" : warn ? \"#f59e0b\" : \"#334155\"\n }\n\n function onMessage(e: MessageEvent) {\n if (!isTrustedOrigin(e.origin, allow)) return\n const msg = parseVisualMessage(e.data)\n if (!msg) return\n if (msg.type === \"init\") {\n parentOrigin = e.origin\n schema = msg.schema\n for (const [name, value] of Object.entries(msg.values)) applyValue(name, value)\n enableHover()\n } else if (msg.type === \"set\") {\n applyValue(msg.name, msg.value)\n }\n }\n\n function enableHover() {\n document.body.setAttribute(\"data-cms-editing\", \"true\")\n injectHoverStyles()\n document.addEventListener(\"click\", onClick, true)\n document.addEventListener(\"input\", onInput, true)\n document.addEventListener(\"blur\", onBlur, true)\n }\n\n window.addEventListener(\"message\", onMessage)\n // Announce readiness to whatever parent embedded us.\n window.parent.postMessage(\n { source: CMS_VISUAL_SOURCE, type: \"ready\", fields: discoverFields(document) },\n \"*\" // ready carries no data; parent validates origin on its side\n )\n\n return () => {\n window.removeEventListener(\"message\", onMessage)\n document.removeEventListener(\"click\", onClick, true)\n document.removeEventListener(\"input\", onInput, true)\n document.removeEventListener(\"blur\", onBlur, true)\n document.body.removeAttribute(\"data-cms-editing\")\n counter.remove()\n }\n}\n\nfunction createCounterEl(): HTMLElement {\n const el = document.createElement(\"div\")\n el.setAttribute(\"data-cms-counter\", \"\")\n Object.assign(el.style, {\n position: \"absolute\", display: \"none\", zIndex: \"2147483647\",\n padding: \"2px 6px\", borderRadius: \"4px\", color: \"#fff\",\n font: \"12px/1.4 system-ui, sans-serif\", pointerEvents: \"none\",\n } as CSSStyleDeclaration)\n document.body.appendChild(el)\n return el\n}\n\nfunction injectHoverStyles() {\n if (document.getElementById(\"cms-visual-styles\")) return\n const s = document.createElement(\"style\")\n s.id = \"cms-visual-styles\"\n s.textContent = `\n [data-cms-editing] [data-cms-field]{outline:1px dashed rgba(99,102,241,.6);outline-offset:2px;cursor:text}\n [data-cms-editing] [data-cms-field][data-cms-type=\"image\"]{cursor:pointer}\n [data-cms-editing] [data-cms-field]:hover{outline:2px solid #6366f1}\n `\n document.head.appendChild(s)\n}\n\nfunction placeCaretAtEnd(el: HTMLElement) {\n const range = document.createRange()\n range.selectNodeContents(el)\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,202 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/visual-editing/react.tsx
5
+ import { useEffect } from "react";
6
+
7
+ // src/visual-editing/protocol.ts
8
+ var CMS_VISUAL_SOURCE = "cms-visual";
9
+ var DEFAULT_TRUSTED_ORIGINS = [
10
+ "https://cms.distinctstudio.co.nz",
11
+ "https://distinctcms.com"
12
+ ];
13
+ function isTrustedOrigin(origin, allowlist) {
14
+ if (allowlist.includes(origin)) return true;
15
+ return /^https?:\/\/localhost(:\d+)?$/.test(origin);
16
+ }
17
+ var VALID_VISUAL_TYPES = /* @__PURE__ */ new Set(["ready", "edit", "pick-image", "init", "set"]);
18
+ function parseVisualMessage(data) {
19
+ if (typeof data === "object" && data !== null && data.source === CMS_VISUAL_SOURCE && typeof data.type === "string" && VALID_VISUAL_TYPES.has(data.type)) {
20
+ return data;
21
+ }
22
+ return null;
23
+ }
24
+
25
+ // src/visual-editing/dom.ts
26
+ function discoverFields(root) {
27
+ const nodes = root.querySelectorAll("[data-cms-field]");
28
+ const fields = [];
29
+ nodes.forEach((el) => {
30
+ const name = el.getAttribute("data-cms-field");
31
+ if (!name) return;
32
+ const type = el.getAttribute("data-cms-type") === "image" ? "image" : "text";
33
+ fields.push({ name, type });
34
+ });
35
+ return fields;
36
+ }
37
+ function enforceMaxLength(value, max) {
38
+ if (max == null) return value;
39
+ return value.length > max ? value.slice(0, Math.max(0, max)) : value;
40
+ }
41
+
42
+ // src/visual-editing/bridge.ts
43
+ function createVisualBridge(options = {}) {
44
+ if (typeof window === "undefined" || window.parent === window) return () => {
45
+ };
46
+ const allow = options.trustedOrigins ?? DEFAULT_TRUSTED_ORIGINS;
47
+ let parentOrigin = null;
48
+ let schema = {};
49
+ let activeEl = null;
50
+ const counter = createCounterEl();
51
+ function post(msg) {
52
+ if (parentOrigin) window.parent.postMessage(msg, parentOrigin);
53
+ }
54
+ function elFor(name) {
55
+ return document.querySelector(`[data-cms-field="${CSS.escape(name)}"]`);
56
+ }
57
+ function applyValue(name, value) {
58
+ const el = elFor(name);
59
+ if (!el) return;
60
+ if (el.getAttribute("data-cms-type") === "image") {
61
+ if (el instanceof HTMLImageElement) el.src = value;
62
+ } else {
63
+ el.textContent = value;
64
+ }
65
+ }
66
+ function onClick(e) {
67
+ const el = e.target?.closest("[data-cms-field]");
68
+ if (!el || !parentOrigin) return;
69
+ const name = el.getAttribute("data-cms-field");
70
+ e.preventDefault();
71
+ if (el.getAttribute("data-cms-type") === "image") {
72
+ post({ source: CMS_VISUAL_SOURCE, type: "pick-image", name });
73
+ return;
74
+ }
75
+ activeEl = el;
76
+ el.setAttribute("contenteditable", "true");
77
+ el.focus();
78
+ positionCounter(el, name);
79
+ }
80
+ function onInput(e) {
81
+ const el = e.target;
82
+ const name = el.getAttribute?.("data-cms-field");
83
+ if (!name || el !== activeEl) return;
84
+ const max = schema[name]?.max_length;
85
+ const clamped = enforceMaxLength(el.textContent ?? "", max);
86
+ if (clamped !== el.textContent) {
87
+ el.textContent = clamped;
88
+ placeCaretAtEnd(el);
89
+ }
90
+ updateCounter(name, clamped.length);
91
+ post({ source: CMS_VISUAL_SOURCE, type: "edit", name, value: clamped, length: clamped.length });
92
+ }
93
+ function onBlur(e) {
94
+ const el = e.target;
95
+ if (el === activeEl) {
96
+ el.removeAttribute("contenteditable");
97
+ activeEl = null;
98
+ counter.style.display = "none";
99
+ }
100
+ }
101
+ function positionCounter(el, name) {
102
+ const r = el.getBoundingClientRect();
103
+ counter.style.top = `${window.scrollY + r.bottom + 4}px`;
104
+ counter.style.left = `${window.scrollX + r.left}px`;
105
+ counter.style.display = "block";
106
+ updateCounter(name, (el.textContent ?? "").length);
107
+ }
108
+ function updateCounter(name, len) {
109
+ const f = schema[name];
110
+ const rec = f?.recommended_length;
111
+ const max = f?.max_length;
112
+ counter.textContent = max ? `${len} / ${rec ?? max}${rec && max ? ` \xB7 max ${max}` : ""}` : `${len}`;
113
+ const over = max != null && len >= max;
114
+ const warn = !over && rec != null && len > rec;
115
+ counter.style.background = over ? "#ef4444" : warn ? "#f59e0b" : "#334155";
116
+ }
117
+ function onMessage(e) {
118
+ if (!isTrustedOrigin(e.origin, allow)) return;
119
+ const msg = parseVisualMessage(e.data);
120
+ if (!msg) return;
121
+ if (msg.type === "init") {
122
+ parentOrigin = e.origin;
123
+ schema = msg.schema;
124
+ for (const [name, value] of Object.entries(msg.values)) applyValue(name, value);
125
+ enableHover();
126
+ } else if (msg.type === "set") {
127
+ applyValue(msg.name, msg.value);
128
+ }
129
+ }
130
+ function enableHover() {
131
+ document.body.setAttribute("data-cms-editing", "true");
132
+ injectHoverStyles();
133
+ document.addEventListener("click", onClick, true);
134
+ document.addEventListener("input", onInput, true);
135
+ document.addEventListener("blur", onBlur, true);
136
+ }
137
+ window.addEventListener("message", onMessage);
138
+ window.parent.postMessage(
139
+ { source: CMS_VISUAL_SOURCE, type: "ready", fields: discoverFields(document) },
140
+ "*"
141
+ // ready carries no data; parent validates origin on its side
142
+ );
143
+ return () => {
144
+ window.removeEventListener("message", onMessage);
145
+ document.removeEventListener("click", onClick, true);
146
+ document.removeEventListener("input", onInput, true);
147
+ document.removeEventListener("blur", onBlur, true);
148
+ document.body.removeAttribute("data-cms-editing");
149
+ counter.remove();
150
+ };
151
+ }
152
+ function createCounterEl() {
153
+ const el = document.createElement("div");
154
+ el.setAttribute("data-cms-counter", "");
155
+ Object.assign(el.style, {
156
+ position: "absolute",
157
+ display: "none",
158
+ zIndex: "2147483647",
159
+ padding: "2px 6px",
160
+ borderRadius: "4px",
161
+ color: "#fff",
162
+ font: "12px/1.4 system-ui, sans-serif",
163
+ pointerEvents: "none"
164
+ });
165
+ document.body.appendChild(el);
166
+ return el;
167
+ }
168
+ function injectHoverStyles() {
169
+ if (document.getElementById("cms-visual-styles")) return;
170
+ const s = document.createElement("style");
171
+ s.id = "cms-visual-styles";
172
+ s.textContent = `
173
+ [data-cms-editing] [data-cms-field]{outline:1px dashed rgba(99,102,241,.6);outline-offset:2px;cursor:text}
174
+ [data-cms-editing] [data-cms-field][data-cms-type="image"]{cursor:pointer}
175
+ [data-cms-editing] [data-cms-field]:hover{outline:2px solid #6366f1}
176
+ `;
177
+ document.head.appendChild(s);
178
+ }
179
+ function placeCaretAtEnd(el) {
180
+ const range = document.createRange();
181
+ range.selectNodeContents(el);
182
+ range.collapse(false);
183
+ const sel = window.getSelection();
184
+ sel?.removeAllRanges();
185
+ sel?.addRange(range);
186
+ }
187
+
188
+ // src/visual-editing/react.tsx
189
+ function CmsVisualBridge({ trustedOrigins, param = "cms_preview" }) {
190
+ useEffect(() => {
191
+ if (typeof window === "undefined") return;
192
+ const params = new URLSearchParams(window.location.search);
193
+ if (!params.has(param)) return;
194
+ const cleanup = createVisualBridge({ trustedOrigins });
195
+ return cleanup;
196
+ }, [trustedOrigins, param]);
197
+ return null;
198
+ }
199
+ export {
200
+ CmsVisualBridge
201
+ };
202
+ //# sourceMappingURL=visual-editing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/visual-editing/react.tsx","../src/visual-editing/protocol.ts","../src/visual-editing/dom.ts","../src/visual-editing/bridge.ts"],"sourcesContent":["// packages/client/src/visual-editing/react.tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { createVisualBridge } from \"./bridge\"\n\nexport interface CmsVisualBridgeProps {\n /** Override the CMS origins the bridge trusts (defaults to Distinct CMS origins + localhost). */\n trustedOrigins?: string[]\n /** Query param that activates preview mode. Defaults to \"cms_preview\". */\n param?: string\n}\n\n/**\n * Mount once in your site (e.g. root layout). Renders nothing and stays inert\n * for normal visitors — it only activates when the page is loaded with the\n * preview param inside a trusted CMS frame.\n */\nexport function CmsVisualBridge({ trustedOrigins, param = \"cms_preview\" }: CmsVisualBridgeProps) {\n useEffect(() => {\n if (typeof window === \"undefined\") return\n const params = new URLSearchParams(window.location.search)\n if (!params.has(param)) return\n const cleanup = createVisualBridge({ trustedOrigins })\n return cleanup\n }, [trustedOrigins, param])\n return null\n}\n","/** Discriminator stamped on every visual-editing postMessage. */\nexport const CMS_VISUAL_SOURCE = \"cms-visual\" as const\n\n/** CMS origins the bridge will trust by default. */\nexport const DEFAULT_TRUSTED_ORIGINS: readonly string[] = [\n \"https://cms.distinctstudio.co.nz\",\n \"https://distinctcms.com\",\n]\n\nexport type VisualFieldType = \"text\" | \"image\"\n\n/** Compact wire type announced in `ready`; the schema adds length constraints. */\nexport interface VisualFieldMeta {\n name: string\n type: VisualFieldType\n}\n\nexport interface VisualFieldSchema {\n name: string\n type: VisualFieldType\n max_length?: number\n recommended_length?: number\n}\n\n// bridge -> parent\nexport interface ReadyMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"ready\"\n fields: VisualFieldMeta[]\n}\nexport interface EditMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"edit\"\n name: string\n value: string\n length: number\n}\nexport interface PickImageMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"pick-image\"\n name: string\n}\n\n// parent -> bridge\nexport interface InitMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"init\"\n values: Record<string, string>\n schema: Record<string, VisualFieldSchema>\n}\nexport interface SetMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"set\"\n name: string\n value: string\n}\n\nexport type BridgeOutbound = ReadyMessage | EditMessage | PickImageMessage\nexport type BridgeInbound = InitMessage | SetMessage\nexport type VisualMessage = BridgeOutbound | BridgeInbound\n\n/** Exact-match origin allowlist, plus any localhost port for local dev. */\nexport function isTrustedOrigin(\n origin: string,\n allowlist: readonly string[]\n): boolean {\n if (allowlist.includes(origin)) return true\n return /^https?:\\/\\/localhost(:\\d+)?$/.test(origin)\n}\n\nconst VALID_VISUAL_TYPES = new Set([\"ready\", \"edit\", \"pick-image\", \"init\", \"set\"])\n\n/** Narrow an unknown postMessage payload to a visual message, or null. */\nexport function parseVisualMessage(data: unknown): VisualMessage | null {\n if (\n typeof data === \"object\" &&\n data !== null &&\n (data as Record<string, unknown>).source === CMS_VISUAL_SOURCE &&\n typeof (data as Record<string, unknown>).type === \"string\" &&\n VALID_VISUAL_TYPES.has((data as Record<string, unknown>).type as string)\n ) {\n return data as VisualMessage\n }\n return null\n}\n","import type { VisualFieldMeta } from \"./protocol\"\n\n/** Read every [data-cms-field] element in document order. */\nexport function discoverFields(root: Document | HTMLElement): VisualFieldMeta[] {\n const nodes = root.querySelectorAll<HTMLElement>(\"[data-cms-field]\")\n const fields: VisualFieldMeta[] = []\n nodes.forEach((el) => {\n const name = el.getAttribute(\"data-cms-field\")\n if (!name) return\n const type = el.getAttribute(\"data-cms-type\") === \"image\" ? \"image\" : \"text\"\n fields.push({ name, type })\n })\n return fields\n}\n\n/** Truncate text to a hard max. Undefined max means no limit. */\nexport function enforceMaxLength(value: string, max: number | undefined): string {\n if (max == null) return value\n return value.length > max ? value.slice(0, Math.max(0, max)) : value\n}\n","// packages/client/src/visual-editing/bridge.ts\nimport {\n CMS_VISUAL_SOURCE,\n DEFAULT_TRUSTED_ORIGINS,\n isTrustedOrigin,\n parseVisualMessage,\n type BridgeOutbound,\n type VisualFieldSchema,\n} from \"./protocol\"\nimport { discoverFields, enforceMaxLength } from \"./dom\"\n\nexport interface VisualBridgeOptions {\n trustedOrigins?: string[]\n}\n\n/**\n * Mounts the in-page editor bridge. Dormant until a trusted CMS parent sends\n * `init`. Returns a cleanup function. No-op when not running inside a frame.\n */\nexport function createVisualBridge(options: VisualBridgeOptions = {}): () => void {\n if (typeof window === \"undefined\" || window.parent === window) return () => {}\n const allow = options.trustedOrigins ?? DEFAULT_TRUSTED_ORIGINS\n\n let parentOrigin: string | null = null\n let schema: Record<string, VisualFieldSchema> = {}\n let activeEl: HTMLElement | null = null\n const counter = createCounterEl()\n\n function post(msg: BridgeOutbound) {\n if (parentOrigin) window.parent.postMessage(msg, parentOrigin)\n }\n\n function elFor(name: string): HTMLElement | null {\n return document.querySelector<HTMLElement>(`[data-cms-field=\"${CSS.escape(name)}\"]`)\n }\n\n function applyValue(name: string, value: string) {\n const el = elFor(name)\n if (!el) return\n if (el.getAttribute(\"data-cms-type\") === \"image\") {\n if (el instanceof HTMLImageElement) el.src = value\n } else {\n el.textContent = value\n }\n }\n\n function onClick(e: MouseEvent) {\n const el = (e.target as HTMLElement)?.closest<HTMLElement>(\"[data-cms-field]\")\n if (!el || !parentOrigin) return\n const name = el.getAttribute(\"data-cms-field\")!\n e.preventDefault()\n if (el.getAttribute(\"data-cms-type\") === \"image\") {\n post({ source: CMS_VISUAL_SOURCE, type: \"pick-image\", name })\n return\n }\n // text: edit in place\n activeEl = el\n el.setAttribute(\"contenteditable\", \"true\")\n el.focus()\n positionCounter(el, name)\n }\n\n function onInput(e: Event) {\n const el = e.target as HTMLElement\n const name = el.getAttribute?.(\"data-cms-field\")\n if (!name || el !== activeEl) return\n const max = schema[name]?.max_length\n const clamped = enforceMaxLength(el.textContent ?? \"\", max)\n if (clamped !== el.textContent) {\n el.textContent = clamped\n placeCaretAtEnd(el)\n }\n updateCounter(name, clamped.length)\n post({ source: CMS_VISUAL_SOURCE, type: \"edit\", name, value: clamped, length: clamped.length })\n }\n\n function onBlur(e: FocusEvent) {\n const el = e.target as HTMLElement\n if (el === activeEl) {\n el.removeAttribute(\"contenteditable\")\n activeEl = null\n counter.style.display = \"none\"\n }\n }\n\n function positionCounter(el: HTMLElement, name: string) {\n const r = el.getBoundingClientRect()\n counter.style.top = `${window.scrollY + r.bottom + 4}px`\n counter.style.left = `${window.scrollX + r.left}px`\n counter.style.display = \"block\"\n updateCounter(name, (el.textContent ?? \"\").length)\n }\n\n function updateCounter(name: string, len: number) {\n const f = schema[name]\n const rec = f?.recommended_length\n const max = f?.max_length\n counter.textContent = max ? `${len} / ${rec ?? max}${rec && max ? ` · max ${max}` : \"\"}` : `${len}`\n const over = max != null && len >= max\n const warn = !over && rec != null && len > rec\n counter.style.background = over ? \"#ef4444\" : warn ? \"#f59e0b\" : \"#334155\"\n }\n\n function onMessage(e: MessageEvent) {\n if (!isTrustedOrigin(e.origin, allow)) return\n const msg = parseVisualMessage(e.data)\n if (!msg) return\n if (msg.type === \"init\") {\n parentOrigin = e.origin\n schema = msg.schema\n for (const [name, value] of Object.entries(msg.values)) applyValue(name, value)\n enableHover()\n } else if (msg.type === \"set\") {\n applyValue(msg.name, msg.value)\n }\n }\n\n function enableHover() {\n document.body.setAttribute(\"data-cms-editing\", \"true\")\n injectHoverStyles()\n document.addEventListener(\"click\", onClick, true)\n document.addEventListener(\"input\", onInput, true)\n document.addEventListener(\"blur\", onBlur, true)\n }\n\n window.addEventListener(\"message\", onMessage)\n // Announce readiness to whatever parent embedded us.\n window.parent.postMessage(\n { source: CMS_VISUAL_SOURCE, type: \"ready\", fields: discoverFields(document) },\n \"*\" // ready carries no data; parent validates origin on its side\n )\n\n return () => {\n window.removeEventListener(\"message\", onMessage)\n document.removeEventListener(\"click\", onClick, true)\n document.removeEventListener(\"input\", onInput, true)\n document.removeEventListener(\"blur\", onBlur, true)\n document.body.removeAttribute(\"data-cms-editing\")\n counter.remove()\n }\n}\n\nfunction createCounterEl(): HTMLElement {\n const el = document.createElement(\"div\")\n el.setAttribute(\"data-cms-counter\", \"\")\n Object.assign(el.style, {\n position: \"absolute\", display: \"none\", zIndex: \"2147483647\",\n padding: \"2px 6px\", borderRadius: \"4px\", color: \"#fff\",\n font: \"12px/1.4 system-ui, sans-serif\", pointerEvents: \"none\",\n } as CSSStyleDeclaration)\n document.body.appendChild(el)\n return el\n}\n\nfunction injectHoverStyles() {\n if (document.getElementById(\"cms-visual-styles\")) return\n const s = document.createElement(\"style\")\n s.id = \"cms-visual-styles\"\n s.textContent = `\n [data-cms-editing] [data-cms-field]{outline:1px dashed rgba(99,102,241,.6);outline-offset:2px;cursor:text}\n [data-cms-editing] [data-cms-field][data-cms-type=\"image\"]{cursor:pointer}\n [data-cms-editing] [data-cms-field]:hover{outline:2px solid #6366f1}\n `\n document.head.appendChild(s)\n}\n\nfunction placeCaretAtEnd(el: HTMLElement) {\n const range = document.createRange()\n range.selectNodeContents(el)\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distinctagency/cms-client",
3
- "version": "1.23.0",
3
+ "version": "1.25.0",
4
4
  "description": "Client library for Distinct CMS — query content, products, and manage orders",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,6 +24,11 @@
24
24
  "types": "./dist/tracking-scripts.d.ts",
25
25
  "import": "./dist/tracking-scripts.mjs",
26
26
  "require": "./dist/tracking-scripts.js"
27
+ },
28
+ "./visual-editing": {
29
+ "types": "./dist/visual-editing.d.ts",
30
+ "import": "./dist/visual-editing.mjs",
31
+ "require": "./dist/visual-editing.js"
27
32
  }
28
33
  },
29
34
  "scripts": {