@distinctagency/cms-client 1.24.0 → 1.26.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,251 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/visual-editing/react.tsx
23
+ var react_exports = {};
24
+ __export(react_exports, {
25
+ CmsVisualBridge: () => CmsVisualBridge
26
+ });
27
+ module.exports = __toCommonJS(react_exports);
28
+ var import_react = require("react");
29
+
30
+ // src/visual-editing/protocol.ts
31
+ var CMS_VISUAL_SOURCE = "cms-visual";
32
+ var DEFAULT_TRUSTED_ORIGINS = [
33
+ "https://cms.distinctstudio.co.nz",
34
+ "https://distinctcms.com"
35
+ ];
36
+ function isTrustedOrigin(origin, allowlist) {
37
+ if (allowlist.includes(origin)) return true;
38
+ return /^https?:\/\/localhost(:\d+)?$/.test(origin);
39
+ }
40
+ var VALID_VISUAL_TYPES = /* @__PURE__ */ new Set(["ready", "edit", "pick-image", "init", "set"]);
41
+ function parseVisualMessage(data) {
42
+ if (typeof data === "object" && data !== null && data.source === CMS_VISUAL_SOURCE && typeof data.type === "string" && VALID_VISUAL_TYPES.has(data.type)) {
43
+ return data;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ // src/visual-editing/dom.ts
49
+ function discoverFields(root) {
50
+ const nodes = root.querySelectorAll("[data-cms-field]");
51
+ const fields = [];
52
+ nodes.forEach((el) => {
53
+ const name = el.getAttribute("data-cms-field");
54
+ if (!name) return;
55
+ const type = el.getAttribute("data-cms-type") === "image" ? "image" : "text";
56
+ fields.push({ name, type });
57
+ });
58
+ return fields;
59
+ }
60
+ function enforceMaxLength(value, max) {
61
+ if (max == null) return value;
62
+ return value.length > max ? value.slice(0, Math.max(0, max)) : value;
63
+ }
64
+ function resolveFieldAddress(el, root) {
65
+ const field = el.getAttribute("data-cms-field") ?? "";
66
+ const item = el.closest("[data-cms-item]");
67
+ if (!item) return { field };
68
+ const list = item.getAttribute("data-cms-item") ?? "";
69
+ const siblings = Array.from(root.querySelectorAll(`[data-cms-item="${CSS.escape(list)}"]`));
70
+ const index = siblings.indexOf(item);
71
+ return { field, list, index };
72
+ }
73
+
74
+ // src/visual-editing/bridge.ts
75
+ function createVisualBridge(options = {}) {
76
+ if (typeof window === "undefined" || window.parent === window) return () => {
77
+ };
78
+ const allow = options.trustedOrigins ?? DEFAULT_TRUSTED_ORIGINS;
79
+ let parentOrigin = null;
80
+ let schema = {};
81
+ let activeEl = null;
82
+ let activeAddr = null;
83
+ let hoverEnabled = false;
84
+ const counter = createCounterEl();
85
+ function post(msg) {
86
+ if (parentOrigin) window.parent.postMessage(msg, parentOrigin);
87
+ }
88
+ function elFor(name) {
89
+ return document.querySelector(`[data-cms-field="${CSS.escape(name)}"]`);
90
+ }
91
+ function elForAddress(field, list, index) {
92
+ if (list == null || index == null) return elFor(field);
93
+ const items = document.querySelectorAll(`[data-cms-item="${CSS.escape(list)}"]`);
94
+ const item = items[index];
95
+ return item ? item.querySelector(`[data-cms-field="${CSS.escape(field)}"]`) : null;
96
+ }
97
+ function applyValueEl(el, value) {
98
+ if (!el) return;
99
+ if (el.getAttribute("data-cms-type") === "image") {
100
+ if (el instanceof HTMLImageElement) el.src = value;
101
+ } else el.textContent = value;
102
+ }
103
+ function applyValue(name, value) {
104
+ applyValueEl(elFor(name), value);
105
+ }
106
+ function schemaKeyOf(a) {
107
+ return a.list ? `${a.list}.${a.field}` : a.field;
108
+ }
109
+ function onClick(e) {
110
+ const el = e.target?.closest("[data-cms-field]");
111
+ if (!el || !parentOrigin) return;
112
+ e.preventDefault();
113
+ const addr = resolveFieldAddress(el, document);
114
+ if (el.getAttribute("data-cms-type") === "image") {
115
+ post({ source: CMS_VISUAL_SOURCE, type: "pick-image", name: addr.field, list: addr.list, index: addr.index });
116
+ return;
117
+ }
118
+ activeEl = el;
119
+ activeAddr = addr;
120
+ el.setAttribute("contenteditable", "true");
121
+ el.focus();
122
+ positionCounter(el, schemaKeyOf(addr));
123
+ }
124
+ function onInput(e) {
125
+ const el = e.target;
126
+ if (!el.getAttribute?.("data-cms-field") || el !== activeEl) return;
127
+ const addr = activeAddr ?? { field: el.getAttribute("data-cms-field") ?? "" };
128
+ const key = schemaKeyOf(addr);
129
+ const max = schema[key]?.max_length;
130
+ const clamped = enforceMaxLength(el.textContent ?? "", max);
131
+ if (clamped !== el.textContent) {
132
+ el.textContent = clamped;
133
+ placeCaretAtEnd(el);
134
+ }
135
+ updateCounter(key, clamped.length);
136
+ post({ source: CMS_VISUAL_SOURCE, type: "edit", name: addr.field, value: clamped, length: clamped.length, list: addr.list, index: addr.index });
137
+ }
138
+ function onBlur(e) {
139
+ const el = e.target;
140
+ if (el === activeEl) {
141
+ el.removeAttribute("contenteditable");
142
+ activeEl = null;
143
+ activeAddr = null;
144
+ counter.style.display = "none";
145
+ }
146
+ }
147
+ function positionCounter(el, schemaKey) {
148
+ const r = el.getBoundingClientRect();
149
+ counter.style.top = `${window.scrollY + r.bottom + 4}px`;
150
+ counter.style.left = `${window.scrollX + r.left}px`;
151
+ counter.style.display = "block";
152
+ updateCounter(schemaKey, (el.textContent ?? "").length);
153
+ }
154
+ function updateCounter(schemaKey, len) {
155
+ const f = schema[schemaKey];
156
+ const rec = f?.recommended_length;
157
+ const max = f?.max_length;
158
+ counter.textContent = max ? `${len} / ${rec ?? max}${rec && max ? ` \xB7 max ${max}` : ""}` : `${len}`;
159
+ const over = max != null && len >= max;
160
+ const warn = !over && rec != null && len > rec;
161
+ counter.style.background = over ? "#ef4444" : warn ? "#f59e0b" : "#334155";
162
+ }
163
+ function onMessage(e) {
164
+ if (!isTrustedOrigin(e.origin, allow)) return;
165
+ const msg = parseVisualMessage(e.data);
166
+ if (!msg) return;
167
+ if (msg.type === "init") {
168
+ parentOrigin = e.origin;
169
+ schema = msg.schema;
170
+ for (const [name, value] of Object.entries(msg.values)) applyValue(name, value);
171
+ enableHover();
172
+ } else if (msg.type === "set") {
173
+ applyValueEl(elForAddress(msg.name, msg.list, msg.index), msg.value);
174
+ }
175
+ }
176
+ function enableHover() {
177
+ if (hoverEnabled) return;
178
+ hoverEnabled = true;
179
+ document.body.setAttribute("data-cms-editing", "true");
180
+ injectHoverStyles();
181
+ document.addEventListener("click", onClick, true);
182
+ document.addEventListener("input", onInput, true);
183
+ document.addEventListener("blur", onBlur, true);
184
+ }
185
+ window.addEventListener("message", onMessage);
186
+ window.parent.postMessage(
187
+ { source: CMS_VISUAL_SOURCE, type: "ready", fields: discoverFields(document) },
188
+ "*"
189
+ // ready carries no data; parent validates origin on its side
190
+ );
191
+ return () => {
192
+ window.removeEventListener("message", onMessage);
193
+ document.removeEventListener("click", onClick, true);
194
+ document.removeEventListener("input", onInput, true);
195
+ document.removeEventListener("blur", onBlur, true);
196
+ document.body.removeAttribute("data-cms-editing");
197
+ counter.remove();
198
+ };
199
+ }
200
+ function createCounterEl() {
201
+ const el = document.createElement("div");
202
+ el.setAttribute("data-cms-counter", "");
203
+ Object.assign(el.style, {
204
+ position: "absolute",
205
+ display: "none",
206
+ zIndex: "2147483647",
207
+ padding: "2px 6px",
208
+ borderRadius: "4px",
209
+ color: "#fff",
210
+ font: "12px/1.4 system-ui, sans-serif",
211
+ pointerEvents: "none"
212
+ });
213
+ document.body.appendChild(el);
214
+ return el;
215
+ }
216
+ function injectHoverStyles() {
217
+ if (document.getElementById("cms-visual-styles")) return;
218
+ const s = document.createElement("style");
219
+ s.id = "cms-visual-styles";
220
+ s.textContent = `
221
+ [data-cms-editing] [data-cms-field]{outline:1px dashed rgba(99,102,241,.6);outline-offset:2px;cursor:text}
222
+ [data-cms-editing] [data-cms-field][data-cms-type="image"]{cursor:pointer}
223
+ [data-cms-editing] [data-cms-field]:hover{outline:2px solid #6366f1}
224
+ `;
225
+ document.head.appendChild(s);
226
+ }
227
+ function placeCaretAtEnd(el) {
228
+ const range = document.createRange();
229
+ range.selectNodeContents(el);
230
+ range.collapse(false);
231
+ const sel = window.getSelection();
232
+ sel?.removeAllRanges();
233
+ sel?.addRange(range);
234
+ }
235
+
236
+ // src/visual-editing/react.tsx
237
+ function CmsVisualBridge({ trustedOrigins, param = "cms_preview" }) {
238
+ (0, import_react.useEffect)(() => {
239
+ if (typeof window === "undefined") return;
240
+ const params = new URLSearchParams(window.location.search);
241
+ if (!params.has(param)) return;
242
+ const cleanup = createVisualBridge({ trustedOrigins });
243
+ return cleanup;
244
+ }, [trustedOrigins, param]);
245
+ return null;
246
+ }
247
+ // Annotate the CommonJS export names for ESM import in node:
248
+ 0 && (module.exports = {
249
+ CmsVisualBridge
250
+ });
251
+ //# sourceMappingURL=visual-editing.js.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 /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\n}\nexport interface PickImageMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"pick-image\"\n name: string\n /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\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 /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\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\nexport interface FieldAddress {\n field: string\n list?: string\n index?: number\n}\n\n/** Resolve a [data-cms-field] element to a flat field or a repeating-item address. */\nexport function resolveFieldAddress(el: HTMLElement, root: Document | HTMLElement): FieldAddress {\n const field = el.getAttribute(\"data-cms-field\") ?? \"\"\n const item = el.closest<HTMLElement>(\"[data-cms-item]\")\n if (!item) return { field }\n const list = item.getAttribute(\"data-cms-item\") ?? \"\"\n const siblings = Array.from(root.querySelectorAll<HTMLElement>(`[data-cms-item=\"${CSS.escape(list)}\"]`))\n const index = siblings.indexOf(item)\n return { field, list, index }\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, resolveFieldAddress, type FieldAddress } 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 let activeAddr: FieldAddress | null = null\n let hoverEnabled = false\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 elForAddress(field: string, list?: string, index?: number): HTMLElement | null {\n if (list == null || index == null) return elFor(field)\n const items = document.querySelectorAll<HTMLElement>(`[data-cms-item=\"${CSS.escape(list)}\"]`)\n const item = items[index]\n return item ? item.querySelector<HTMLElement>(`[data-cms-field=\"${CSS.escape(field)}\"]`) : null\n }\n\n function applyValueEl(el: HTMLElement | null, value: string) {\n if (!el) return\n if (el.getAttribute(\"data-cms-type\") === \"image\") { if (el instanceof HTMLImageElement) el.src = value }\n else el.textContent = value\n }\n\n function applyValue(name: string, value: string) { applyValueEl(elFor(name), value) }\n\n function schemaKeyOf(a: FieldAddress): string { return a.list ? `${a.list}.${a.field}` : a.field }\n\n function onClick(e: MouseEvent) {\n const el = (e.target as HTMLElement)?.closest<HTMLElement>(\"[data-cms-field]\")\n if (!el || !parentOrigin) return\n e.preventDefault()\n const addr = resolveFieldAddress(el, document)\n if (el.getAttribute(\"data-cms-type\") === \"image\") {\n post({ source: CMS_VISUAL_SOURCE, type: \"pick-image\", name: addr.field, list: addr.list, index: addr.index })\n return\n }\n // text: edit in place\n activeEl = el\n activeAddr = addr\n el.setAttribute(\"contenteditable\", \"true\")\n el.focus()\n positionCounter(el, schemaKeyOf(addr))\n }\n\n function onInput(e: Event) {\n const el = e.target as HTMLElement\n if (!el.getAttribute?.(\"data-cms-field\") || el !== activeEl) return\n const addr = activeAddr ?? { field: el.getAttribute(\"data-cms-field\") ?? \"\" }\n const key = schemaKeyOf(addr)\n const max = schema[key]?.max_length\n const clamped = enforceMaxLength(el.textContent ?? \"\", max)\n if (clamped !== el.textContent) { el.textContent = clamped; placeCaretAtEnd(el) }\n updateCounter(key, clamped.length)\n post({ source: CMS_VISUAL_SOURCE, type: \"edit\", name: addr.field, value: clamped, length: clamped.length, list: addr.list, index: addr.index })\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 activeAddr = null\n counter.style.display = \"none\"\n }\n }\n\n function positionCounter(el: HTMLElement, schemaKey: 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(schemaKey, (el.textContent ?? \"\").length)\n }\n\n function updateCounter(schemaKey: string, len: number) {\n const f = schema[schemaKey]\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 applyValueEl(elForAddress(msg.name, msg.list, msg.index), msg.value)\n }\n }\n\n function enableHover() {\n if (hoverEnabled) return\n hoverEnabled = true\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,227 @@
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
+ function resolveFieldAddress(el, root) {
42
+ const field = el.getAttribute("data-cms-field") ?? "";
43
+ const item = el.closest("[data-cms-item]");
44
+ if (!item) return { field };
45
+ const list = item.getAttribute("data-cms-item") ?? "";
46
+ const siblings = Array.from(root.querySelectorAll(`[data-cms-item="${CSS.escape(list)}"]`));
47
+ const index = siblings.indexOf(item);
48
+ return { field, list, index };
49
+ }
50
+
51
+ // src/visual-editing/bridge.ts
52
+ function createVisualBridge(options = {}) {
53
+ if (typeof window === "undefined" || window.parent === window) return () => {
54
+ };
55
+ const allow = options.trustedOrigins ?? DEFAULT_TRUSTED_ORIGINS;
56
+ let parentOrigin = null;
57
+ let schema = {};
58
+ let activeEl = null;
59
+ let activeAddr = null;
60
+ let hoverEnabled = false;
61
+ const counter = createCounterEl();
62
+ function post(msg) {
63
+ if (parentOrigin) window.parent.postMessage(msg, parentOrigin);
64
+ }
65
+ function elFor(name) {
66
+ return document.querySelector(`[data-cms-field="${CSS.escape(name)}"]`);
67
+ }
68
+ function elForAddress(field, list, index) {
69
+ if (list == null || index == null) return elFor(field);
70
+ const items = document.querySelectorAll(`[data-cms-item="${CSS.escape(list)}"]`);
71
+ const item = items[index];
72
+ return item ? item.querySelector(`[data-cms-field="${CSS.escape(field)}"]`) : null;
73
+ }
74
+ function applyValueEl(el, value) {
75
+ if (!el) return;
76
+ if (el.getAttribute("data-cms-type") === "image") {
77
+ if (el instanceof HTMLImageElement) el.src = value;
78
+ } else el.textContent = value;
79
+ }
80
+ function applyValue(name, value) {
81
+ applyValueEl(elFor(name), value);
82
+ }
83
+ function schemaKeyOf(a) {
84
+ return a.list ? `${a.list}.${a.field}` : a.field;
85
+ }
86
+ function onClick(e) {
87
+ const el = e.target?.closest("[data-cms-field]");
88
+ if (!el || !parentOrigin) return;
89
+ e.preventDefault();
90
+ const addr = resolveFieldAddress(el, document);
91
+ if (el.getAttribute("data-cms-type") === "image") {
92
+ post({ source: CMS_VISUAL_SOURCE, type: "pick-image", name: addr.field, list: addr.list, index: addr.index });
93
+ return;
94
+ }
95
+ activeEl = el;
96
+ activeAddr = addr;
97
+ el.setAttribute("contenteditable", "true");
98
+ el.focus();
99
+ positionCounter(el, schemaKeyOf(addr));
100
+ }
101
+ function onInput(e) {
102
+ const el = e.target;
103
+ if (!el.getAttribute?.("data-cms-field") || el !== activeEl) return;
104
+ const addr = activeAddr ?? { field: el.getAttribute("data-cms-field") ?? "" };
105
+ const key = schemaKeyOf(addr);
106
+ const max = schema[key]?.max_length;
107
+ const clamped = enforceMaxLength(el.textContent ?? "", max);
108
+ if (clamped !== el.textContent) {
109
+ el.textContent = clamped;
110
+ placeCaretAtEnd(el);
111
+ }
112
+ updateCounter(key, clamped.length);
113
+ post({ source: CMS_VISUAL_SOURCE, type: "edit", name: addr.field, value: clamped, length: clamped.length, list: addr.list, index: addr.index });
114
+ }
115
+ function onBlur(e) {
116
+ const el = e.target;
117
+ if (el === activeEl) {
118
+ el.removeAttribute("contenteditable");
119
+ activeEl = null;
120
+ activeAddr = null;
121
+ counter.style.display = "none";
122
+ }
123
+ }
124
+ function positionCounter(el, schemaKey) {
125
+ const r = el.getBoundingClientRect();
126
+ counter.style.top = `${window.scrollY + r.bottom + 4}px`;
127
+ counter.style.left = `${window.scrollX + r.left}px`;
128
+ counter.style.display = "block";
129
+ updateCounter(schemaKey, (el.textContent ?? "").length);
130
+ }
131
+ function updateCounter(schemaKey, len) {
132
+ const f = schema[schemaKey];
133
+ const rec = f?.recommended_length;
134
+ const max = f?.max_length;
135
+ counter.textContent = max ? `${len} / ${rec ?? max}${rec && max ? ` \xB7 max ${max}` : ""}` : `${len}`;
136
+ const over = max != null && len >= max;
137
+ const warn = !over && rec != null && len > rec;
138
+ counter.style.background = over ? "#ef4444" : warn ? "#f59e0b" : "#334155";
139
+ }
140
+ function onMessage(e) {
141
+ if (!isTrustedOrigin(e.origin, allow)) return;
142
+ const msg = parseVisualMessage(e.data);
143
+ if (!msg) return;
144
+ if (msg.type === "init") {
145
+ parentOrigin = e.origin;
146
+ schema = msg.schema;
147
+ for (const [name, value] of Object.entries(msg.values)) applyValue(name, value);
148
+ enableHover();
149
+ } else if (msg.type === "set") {
150
+ applyValueEl(elForAddress(msg.name, msg.list, msg.index), msg.value);
151
+ }
152
+ }
153
+ function enableHover() {
154
+ if (hoverEnabled) return;
155
+ hoverEnabled = true;
156
+ document.body.setAttribute("data-cms-editing", "true");
157
+ injectHoverStyles();
158
+ document.addEventListener("click", onClick, true);
159
+ document.addEventListener("input", onInput, true);
160
+ document.addEventListener("blur", onBlur, true);
161
+ }
162
+ window.addEventListener("message", onMessage);
163
+ window.parent.postMessage(
164
+ { source: CMS_VISUAL_SOURCE, type: "ready", fields: discoverFields(document) },
165
+ "*"
166
+ // ready carries no data; parent validates origin on its side
167
+ );
168
+ return () => {
169
+ window.removeEventListener("message", onMessage);
170
+ document.removeEventListener("click", onClick, true);
171
+ document.removeEventListener("input", onInput, true);
172
+ document.removeEventListener("blur", onBlur, true);
173
+ document.body.removeAttribute("data-cms-editing");
174
+ counter.remove();
175
+ };
176
+ }
177
+ function createCounterEl() {
178
+ const el = document.createElement("div");
179
+ el.setAttribute("data-cms-counter", "");
180
+ Object.assign(el.style, {
181
+ position: "absolute",
182
+ display: "none",
183
+ zIndex: "2147483647",
184
+ padding: "2px 6px",
185
+ borderRadius: "4px",
186
+ color: "#fff",
187
+ font: "12px/1.4 system-ui, sans-serif",
188
+ pointerEvents: "none"
189
+ });
190
+ document.body.appendChild(el);
191
+ return el;
192
+ }
193
+ function injectHoverStyles() {
194
+ if (document.getElementById("cms-visual-styles")) return;
195
+ const s = document.createElement("style");
196
+ s.id = "cms-visual-styles";
197
+ s.textContent = `
198
+ [data-cms-editing] [data-cms-field]{outline:1px dashed rgba(99,102,241,.6);outline-offset:2px;cursor:text}
199
+ [data-cms-editing] [data-cms-field][data-cms-type="image"]{cursor:pointer}
200
+ [data-cms-editing] [data-cms-field]:hover{outline:2px solid #6366f1}
201
+ `;
202
+ document.head.appendChild(s);
203
+ }
204
+ function placeCaretAtEnd(el) {
205
+ const range = document.createRange();
206
+ range.selectNodeContents(el);
207
+ range.collapse(false);
208
+ const sel = window.getSelection();
209
+ sel?.removeAllRanges();
210
+ sel?.addRange(range);
211
+ }
212
+
213
+ // src/visual-editing/react.tsx
214
+ function CmsVisualBridge({ trustedOrigins, param = "cms_preview" }) {
215
+ useEffect(() => {
216
+ if (typeof window === "undefined") return;
217
+ const params = new URLSearchParams(window.location.search);
218
+ if (!params.has(param)) return;
219
+ const cleanup = createVisualBridge({ trustedOrigins });
220
+ return cleanup;
221
+ }, [trustedOrigins, param]);
222
+ return null;
223
+ }
224
+ export {
225
+ CmsVisualBridge
226
+ };
227
+ //# 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 /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\n}\nexport interface PickImageMessage {\n source: typeof CMS_VISUAL_SOURCE\n type: \"pick-image\"\n name: string\n /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\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 /** When the field is inside a repeating group item, the group field name. */\n list?: string\n /** Item index within the group (document order). */\n index?: number\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\nexport interface FieldAddress {\n field: string\n list?: string\n index?: number\n}\n\n/** Resolve a [data-cms-field] element to a flat field or a repeating-item address. */\nexport function resolveFieldAddress(el: HTMLElement, root: Document | HTMLElement): FieldAddress {\n const field = el.getAttribute(\"data-cms-field\") ?? \"\"\n const item = el.closest<HTMLElement>(\"[data-cms-item]\")\n if (!item) return { field }\n const list = item.getAttribute(\"data-cms-item\") ?? \"\"\n const siblings = Array.from(root.querySelectorAll<HTMLElement>(`[data-cms-item=\"${CSS.escape(list)}\"]`))\n const index = siblings.indexOf(item)\n return { field, list, index }\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, resolveFieldAddress, type FieldAddress } 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 let activeAddr: FieldAddress | null = null\n let hoverEnabled = false\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 elForAddress(field: string, list?: string, index?: number): HTMLElement | null {\n if (list == null || index == null) return elFor(field)\n const items = document.querySelectorAll<HTMLElement>(`[data-cms-item=\"${CSS.escape(list)}\"]`)\n const item = items[index]\n return item ? item.querySelector<HTMLElement>(`[data-cms-field=\"${CSS.escape(field)}\"]`) : null\n }\n\n function applyValueEl(el: HTMLElement | null, value: string) {\n if (!el) return\n if (el.getAttribute(\"data-cms-type\") === \"image\") { if (el instanceof HTMLImageElement) el.src = value }\n else el.textContent = value\n }\n\n function applyValue(name: string, value: string) { applyValueEl(elFor(name), value) }\n\n function schemaKeyOf(a: FieldAddress): string { return a.list ? `${a.list}.${a.field}` : a.field }\n\n function onClick(e: MouseEvent) {\n const el = (e.target as HTMLElement)?.closest<HTMLElement>(\"[data-cms-field]\")\n if (!el || !parentOrigin) return\n e.preventDefault()\n const addr = resolveFieldAddress(el, document)\n if (el.getAttribute(\"data-cms-type\") === \"image\") {\n post({ source: CMS_VISUAL_SOURCE, type: \"pick-image\", name: addr.field, list: addr.list, index: addr.index })\n return\n }\n // text: edit in place\n activeEl = el\n activeAddr = addr\n el.setAttribute(\"contenteditable\", \"true\")\n el.focus()\n positionCounter(el, schemaKeyOf(addr))\n }\n\n function onInput(e: Event) {\n const el = e.target as HTMLElement\n if (!el.getAttribute?.(\"data-cms-field\") || el !== activeEl) return\n const addr = activeAddr ?? { field: el.getAttribute(\"data-cms-field\") ?? \"\" }\n const key = schemaKeyOf(addr)\n const max = schema[key]?.max_length\n const clamped = enforceMaxLength(el.textContent ?? \"\", max)\n if (clamped !== el.textContent) { el.textContent = clamped; placeCaretAtEnd(el) }\n updateCounter(key, clamped.length)\n post({ source: CMS_VISUAL_SOURCE, type: \"edit\", name: addr.field, value: clamped, length: clamped.length, list: addr.list, index: addr.index })\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 activeAddr = null\n counter.style.display = \"none\"\n }\n }\n\n function positionCounter(el: HTMLElement, schemaKey: 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(schemaKey, (el.textContent ?? \"\").length)\n }\n\n function updateCounter(schemaKey: string, len: number) {\n const f = schema[schemaKey]\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 applyValueEl(elForAddress(msg.name, msg.list, msg.index), msg.value)\n }\n }\n\n function enableHover() {\n if (hoverEnabled) return\n hoverEnabled = true\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":";;;;AAEA,SAAS,iBAAiB;;;ACDnB,IAAM,oBAAoB;AAG1B,IAAM,0BAA6C;AAAA,EACxD;AAAA,EACA;AACF;AAmEO,SAAS,gBACd,QACA,WACS;AACT,MAAI,UAAU,SAAS,MAAM,EAAG,QAAO;AACvC,SAAO,gCAAgC,KAAK,MAAM;AACpD;AAEA,IAAM,qBAAqB,oBAAI,IAAI,CAAC,SAAS,QAAQ,cAAc,QAAQ,KAAK,CAAC;AAG1E,SAAS,mBAAmB,MAAqC;AACtE,MACE,OAAO,SAAS,YAChB,SAAS,QACR,KAAiC,WAAW,qBAC7C,OAAQ,KAAiC,SAAS,YAClD,mBAAmB,IAAK,KAAiC,IAAc,GACvE;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AC7FO,SAAS,eAAe,MAAiD;AAC9E,QAAM,QAAQ,KAAK,iBAA8B,kBAAkB;AACnE,QAAM,SAA4B,CAAC;AACnC,QAAM,QAAQ,CAAC,OAAO;AACpB,UAAM,OAAO,GAAG,aAAa,gBAAgB;AAC7C,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,GAAG,aAAa,eAAe,MAAM,UAAU,UAAU;AACtE,WAAO,KAAK,EAAE,MAAM,KAAK,CAAC;AAAA,EAC5B,CAAC;AACD,SAAO;AACT;AAGO,SAAS,iBAAiB,OAAe,KAAiC;AAC/E,MAAI,OAAO,KAAM,QAAO;AACxB,SAAO,MAAM,SAAS,MAAM,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI;AACjE;AASO,SAAS,oBAAoB,IAAiB,MAA4C;AAC/F,QAAM,QAAQ,GAAG,aAAa,gBAAgB,KAAK;AACnD,QAAM,OAAO,GAAG,QAAqB,iBAAiB;AACtD,MAAI,CAAC,KAAM,QAAO,EAAE,MAAM;AAC1B,QAAM,OAAO,KAAK,aAAa,eAAe,KAAK;AACnD,QAAM,WAAW,MAAM,KAAK,KAAK,iBAA8B,mBAAmB,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC;AACvG,QAAM,QAAQ,SAAS,QAAQ,IAAI;AACnC,SAAO,EAAE,OAAO,MAAM,MAAM;AAC9B;;;ACjBO,SAAS,mBAAmB,UAA+B,CAAC,GAAe;AAChF,MAAI,OAAO,WAAW,eAAe,OAAO,WAAW,OAAQ,QAAO,MAAM;AAAA,EAAC;AAC7E,QAAM,QAAQ,QAAQ,kBAAkB;AAExC,MAAI,eAA8B;AAClC,MAAI,SAA4C,CAAC;AACjD,MAAI,WAA+B;AACnC,MAAI,aAAkC;AACtC,MAAI,eAAe;AACnB,QAAM,UAAU,gBAAgB;AAEhC,WAAS,KAAK,KAAqB;AACjC,QAAI,aAAc,QAAO,OAAO,YAAY,KAAK,YAAY;AAAA,EAC/D;AAEA,WAAS,MAAM,MAAkC;AAC/C,WAAO,SAAS,cAA2B,oBAAoB,IAAI,OAAO,IAAI,CAAC,IAAI;AAAA,EACrF;AAEA,WAAS,aAAa,OAAe,MAAe,OAAoC;AACtF,QAAI,QAAQ,QAAQ,SAAS,KAAM,QAAO,MAAM,KAAK;AACrD,UAAM,QAAQ,SAAS,iBAA8B,mBAAmB,IAAI,OAAO,IAAI,CAAC,IAAI;AAC5F,UAAM,OAAO,MAAM,KAAK;AACxB,WAAO,OAAO,KAAK,cAA2B,oBAAoB,IAAI,OAAO,KAAK,CAAC,IAAI,IAAI;AAAA,EAC7F;AAEA,WAAS,aAAa,IAAwB,OAAe;AAC3D,QAAI,CAAC,GAAI;AACT,QAAI,GAAG,aAAa,eAAe,MAAM,SAAS;AAAE,UAAI,cAAc,iBAAkB,IAAG,MAAM;AAAA,IAAM,MAClG,IAAG,cAAc;AAAA,EACxB;AAEA,WAAS,WAAW,MAAc,OAAe;AAAE,iBAAa,MAAM,IAAI,GAAG,KAAK;AAAA,EAAE;AAEpF,WAAS,YAAY,GAAyB;AAAE,WAAO,EAAE,OAAO,GAAG,EAAE,IAAI,IAAI,EAAE,KAAK,KAAK,EAAE;AAAA,EAAM;AAEjG,WAAS,QAAQ,GAAe;AAC9B,UAAM,KAAM,EAAE,QAAwB,QAAqB,kBAAkB;AAC7E,QAAI,CAAC,MAAM,CAAC,aAAc;AAC1B,MAAE,eAAe;AACjB,UAAM,OAAO,oBAAoB,IAAI,QAAQ;AAC7C,QAAI,GAAG,aAAa,eAAe,MAAM,SAAS;AAChD,WAAK,EAAE,QAAQ,mBAAmB,MAAM,cAAc,MAAM,KAAK,OAAO,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC;AAC5G;AAAA,IACF;AAEA,eAAW;AACX,iBAAa;AACb,OAAG,aAAa,mBAAmB,MAAM;AACzC,OAAG,MAAM;AACT,oBAAgB,IAAI,YAAY,IAAI,CAAC;AAAA,EACvC;AAEA,WAAS,QAAQ,GAAU;AACzB,UAAM,KAAK,EAAE;AACb,QAAI,CAAC,GAAG,eAAe,gBAAgB,KAAK,OAAO,SAAU;AAC7D,UAAM,OAAO,cAAc,EAAE,OAAO,GAAG,aAAa,gBAAgB,KAAK,GAAG;AAC5E,UAAM,MAAM,YAAY,IAAI;AAC5B,UAAM,MAAM,OAAO,GAAG,GAAG;AACzB,UAAM,UAAU,iBAAiB,GAAG,eAAe,IAAI,GAAG;AAC1D,QAAI,YAAY,GAAG,aAAa;AAAE,SAAG,cAAc;AAAS,sBAAgB,EAAE;AAAA,IAAE;AAChF,kBAAc,KAAK,QAAQ,MAAM;AACjC,SAAK,EAAE,QAAQ,mBAAmB,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,SAAS,QAAQ,QAAQ,QAAQ,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,EAChJ;AAEA,WAAS,OAAO,GAAe;AAC7B,UAAM,KAAK,EAAE;AACb,QAAI,OAAO,UAAU;AACnB,SAAG,gBAAgB,iBAAiB;AACpC,iBAAW;AACX,mBAAa;AACb,cAAQ,MAAM,UAAU;AAAA,IAC1B;AAAA,EACF;AAEA,WAAS,gBAAgB,IAAiB,WAAmB;AAC3D,UAAM,IAAI,GAAG,sBAAsB;AACnC,YAAQ,MAAM,MAAM,GAAG,OAAO,UAAU,EAAE,SAAS,CAAC;AACpD,YAAQ,MAAM,OAAO,GAAG,OAAO,UAAU,EAAE,IAAI;AAC/C,YAAQ,MAAM,UAAU;AACxB,kBAAc,YAAY,GAAG,eAAe,IAAI,MAAM;AAAA,EACxD;AAEA,WAAS,cAAc,WAAmB,KAAa;AACrD,UAAM,IAAI,OAAO,SAAS;AAC1B,UAAM,MAAM,GAAG;AACf,UAAM,MAAM,GAAG;AACf,YAAQ,cAAc,MAAM,GAAG,GAAG,MAAM,OAAO,GAAG,GAAG,OAAO,MAAM,aAAU,GAAG,KAAK,EAAE,KAAK,GAAG,GAAG;AACjG,UAAM,OAAO,OAAO,QAAQ,OAAO;AACnC,UAAM,OAAO,CAAC,QAAQ,OAAO,QAAQ,MAAM;AAC3C,YAAQ,MAAM,aAAa,OAAO,YAAY,OAAO,YAAY;AAAA,EACnE;AAEA,WAAS,UAAU,GAAiB;AAClC,QAAI,CAAC,gBAAgB,EAAE,QAAQ,KAAK,EAAG;AACvC,UAAM,MAAM,mBAAmB,EAAE,IAAI;AACrC,QAAI,CAAC,IAAK;AACV,QAAI,IAAI,SAAS,QAAQ;AACvB,qBAAe,EAAE;AACjB,eAAS,IAAI;AACb,iBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,MAAM,EAAG,YAAW,MAAM,KAAK;AAC9E,kBAAY;AAAA,IACd,WAAW,IAAI,SAAS,OAAO;AAC7B,mBAAa,aAAa,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK,GAAG,IAAI,KAAK;AAAA,IACrE;AAAA,EACF;AAEA,WAAS,cAAc;AACrB,QAAI,aAAc;AAClB,mBAAe;AACf,aAAS,KAAK,aAAa,oBAAoB,MAAM;AACrD,sBAAkB;AAClB,aAAS,iBAAiB,SAAS,SAAS,IAAI;AAChD,aAAS,iBAAiB,SAAS,SAAS,IAAI;AAChD,aAAS,iBAAiB,QAAQ,QAAQ,IAAI;AAAA,EAChD;AAEA,SAAO,iBAAiB,WAAW,SAAS;AAE5C,SAAO,OAAO;AAAA,IACZ,EAAE,QAAQ,mBAAmB,MAAM,SAAS,QAAQ,eAAe,QAAQ,EAAE;AAAA,IAC7E;AAAA;AAAA,EACF;AAEA,SAAO,MAAM;AACX,WAAO,oBAAoB,WAAW,SAAS;AAC/C,aAAS,oBAAoB,SAAS,SAAS,IAAI;AACnD,aAAS,oBAAoB,SAAS,SAAS,IAAI;AACnD,aAAS,oBAAoB,QAAQ,QAAQ,IAAI;AACjD,aAAS,KAAK,gBAAgB,kBAAkB;AAChD,YAAQ,OAAO;AAAA,EACjB;AACF;AAEA,SAAS,kBAA+B;AACtC,QAAM,KAAK,SAAS,cAAc,KAAK;AACvC,KAAG,aAAa,oBAAoB,EAAE;AACtC,SAAO,OAAO,GAAG,OAAO;AAAA,IACtB,UAAU;AAAA,IAAY,SAAS;AAAA,IAAQ,QAAQ;AAAA,IAC/C,SAAS;AAAA,IAAW,cAAc;AAAA,IAAO,OAAO;AAAA,IAChD,MAAM;AAAA,IAAkC,eAAe;AAAA,EACzD,CAAwB;AACxB,WAAS,KAAK,YAAY,EAAE;AAC5B,SAAO;AACT;AAEA,SAAS,oBAAoB;AAC3B,MAAI,SAAS,eAAe,mBAAmB,EAAG;AAClD,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAAA;AAAA;AAAA;AAAA;AAKhB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEA,SAAS,gBAAgB,IAAiB;AACxC,QAAM,QAAQ,SAAS,YAAY;AACnC,QAAM,mBAAmB,EAAE;AAC3B,QAAM,SAAS,KAAK;AACpB,QAAM,MAAM,OAAO,aAAa;AAChC,OAAK,gBAAgB;AACrB,OAAK,SAAS,KAAK;AACrB;;;AHvKO,SAAS,gBAAgB,EAAE,gBAAgB,QAAQ,cAAc,GAAyB;AAC/F,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACzD,QAAI,CAAC,OAAO,IAAI,KAAK,EAAG;AACxB,UAAM,UAAU,mBAAmB,EAAE,eAAe,CAAC;AACrD,WAAO;AAAA,EACT,GAAG,CAAC,gBAAgB,KAAK,CAAC;AAC1B,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distinctagency/cms-client",
3
- "version": "1.24.0",
3
+ "version": "1.26.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": {