@cmssy/react 0.17.0 → 0.18.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.
package/dist/client.cjs CHANGED
@@ -44,22 +44,37 @@ var PROTOCOL_VERSION = 2;
44
44
 
45
45
  // src/bridge/messages.ts
46
46
  function normalizeOrigin(origin) {
47
- if (origin === "*") return "*";
47
+ const trimmed = origin.trim();
48
+ if (trimmed === "*") return "*";
48
49
  try {
49
- return new URL(origin).origin;
50
+ return new URL(trimmed).origin;
50
51
  } catch {
51
- return origin;
52
+ return trimmed;
52
53
  }
53
54
  }
54
55
  function postToEditor(target, editorOrigin, message) {
55
56
  target.postMessage(message, normalizeOrigin(editorOrigin));
56
57
  }
58
+ function isOriginAllowed(origin, allowed) {
59
+ const list = Array.isArray(allowed) ? allowed : [allowed];
60
+ const actual = normalizeOrigin(origin);
61
+ return list.some((candidate) => {
62
+ const expected = normalizeOrigin(candidate);
63
+ return expected === "*" || expected === actual;
64
+ });
65
+ }
66
+ function resolveInitialTarget(editorOrigin) {
67
+ const list = (Array.isArray(editorOrigin) ? editorOrigin : [editorOrigin]).map((origin) => normalizeOrigin(origin));
68
+ if (list.includes("*")) return "*";
69
+ if (list.length === 1) return list[0];
70
+ const referrerOrigin = typeof document !== "undefined" && document.referrer ? normalizeOrigin(document.referrer) : "";
71
+ return list.find((origin) => origin === referrerOrigin) ?? list[0];
72
+ }
57
73
  function isObject(value) {
58
74
  return typeof value === "object" && value !== null && !Array.isArray(value);
59
75
  }
60
76
  function parseEditorMessage(data, origin, expectedOrigin) {
61
- const expected = normalizeOrigin(expectedOrigin);
62
- if (expected !== "*" && origin !== expected) return null;
77
+ if (!isOriginAllowed(origin, expectedOrigin)) return null;
63
78
  if (!isObject(data)) return null;
64
79
  switch (data.type) {
65
80
  case "cmssy:select":
@@ -130,6 +145,18 @@ function collectRects() {
130
145
  }
131
146
  return rects;
132
147
  }
148
+ function findBlockEl(blockId) {
149
+ if (typeof document === "undefined") return null;
150
+ const escaped = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(blockId) : blockId.replace(/["\\]/g, "\\$&");
151
+ try {
152
+ return document.querySelector(`[data-block-id="${escaped}"]`);
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function prefersReducedMotion() {
158
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
159
+ }
133
160
  function collectLayoutBlocks(rects, pageIds) {
134
161
  const out = [];
135
162
  if (typeof document === "undefined") return out;
@@ -174,7 +201,9 @@ function useEditBridge(page, config) {
174
201
  react.useEffect(() => {
175
202
  if (typeof window === "undefined" || window.parent === window) return;
176
203
  const { editorOrigin } = config;
177
- if (editorOrigin === "*" && typeof console !== "undefined") {
204
+ let postTarget = resolveInitialTarget(editorOrigin);
205
+ const isWildcard = Array.isArray(editorOrigin) ? editorOrigin.includes("*") : editorOrigin === "*";
206
+ if (isWildcard && typeof console !== "undefined") {
178
207
  console.warn(
179
208
  "[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
180
209
  );
@@ -183,7 +212,7 @@ function useEditBridge(page, config) {
183
212
  try {
184
213
  const rects = collectRects();
185
214
  const pageIds = new Set(blocks.map((b) => b.id));
186
- postToEditor(window.parent, editorOrigin, {
215
+ postToEditor(window.parent, postTarget, {
187
216
  type: "cmssy:ready",
188
217
  protocolVersion: PROTOCOL_VERSION,
189
218
  blocks: [
@@ -211,6 +240,7 @@ function useEditBridge(page, config) {
211
240
  editorOrigin
212
241
  );
213
242
  if (!message) return;
243
+ postTarget = event.origin;
214
244
  if (message.type === "cmssy:patch") {
215
245
  if (message.layoutPosition !== void 0) return;
216
246
  setPatches((prev) => ({
@@ -235,6 +265,10 @@ function useEditBridge(page, config) {
235
265
  } else if (message.type === "cmssy:select") {
236
266
  setSelected(message.blockId);
237
267
  selectedIdRef.current = message.blockId;
268
+ findBlockEl(message.blockId)?.scrollIntoView({
269
+ behavior: prefersReducedMotion() ? "auto" : "smooth",
270
+ block: "nearest"
271
+ });
238
272
  } else if (message.type === "cmssy:insert") {
239
273
  setInserted((prev) => {
240
274
  const next = prev.filter((b) => b.blockId !== message.blockId);
@@ -268,7 +302,7 @@ function useEditBridge(page, config) {
268
302
  const r = el.getBoundingClientRect();
269
303
  const layoutPosition = el.getAttribute("data-layout-position");
270
304
  try {
271
- postToEditor(window.parent, editorOrigin, {
305
+ postToEditor(window.parent, postTarget, {
272
306
  type: "cmssy:click",
273
307
  blockId: id,
274
308
  rect: { x: r.x, y: r.y, width: r.width, height: r.height },
@@ -287,13 +321,11 @@ function useEditBridge(page, config) {
287
321
  boundsRaf = 0;
288
322
  const id = selectedIdRef.current;
289
323
  if (!id) return;
290
- const el = document.querySelector(
291
- `[data-block-id="${id.replace(/["\\]/g, "\\$&")}"]`
292
- );
324
+ const el = findBlockEl(id);
293
325
  if (!el) return;
294
326
  const r = el.getBoundingClientRect();
295
327
  try {
296
- postToEditor(window.parent, editorOrigin, {
328
+ postToEditor(window.parent, postTarget, {
297
329
  type: "cmssy:bounds",
298
330
  blockId: id,
299
331
  rect: { x: r.x, y: r.y, width: r.width, height: r.height }
@@ -383,7 +415,9 @@ function useDragAgent(config) {
383
415
  react.useEffect(() => {
384
416
  if (typeof window === "undefined" || window.parent === window) return;
385
417
  const { editorOrigin } = config;
386
- if (editorOrigin === "*" && typeof console !== "undefined") {
418
+ let postTarget = resolveInitialTarget(editorOrigin);
419
+ const isWildcard = Array.isArray(editorOrigin) ? editorOrigin.includes("*") : editorOrigin === "*";
420
+ if (isWildcard && typeof console !== "undefined") {
387
421
  console.warn(
388
422
  "[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
389
423
  );
@@ -419,7 +453,7 @@ function useDragAgent(config) {
419
453
  updateDropY(null);
420
454
  resolver.invalidate();
421
455
  try {
422
- postToEditor(window.parent, editorOrigin, {
456
+ postToEditor(window.parent, postTarget, {
423
457
  type: "cmssy:move",
424
458
  protocolVersion: PROTOCOL_VERSION,
425
459
  blockId,
@@ -441,6 +475,7 @@ function useDragAgent(config) {
441
475
  editorOrigin
442
476
  );
443
477
  if (!message) return;
478
+ postTarget = event.origin;
444
479
  if (message.type === "cmssy:drag-over") {
445
480
  const edge = 64;
446
481
  const step = 20;
@@ -452,7 +487,7 @@ function useDragAgent(config) {
452
487
  const { index, y } = resolver.resolve(message.y);
453
488
  updateDropY(y);
454
489
  try {
455
- postToEditor(window.parent, editorOrigin, {
490
+ postToEditor(window.parent, postTarget, {
456
491
  type: "cmssy:drag-index",
457
492
  protocolVersion: PROTOCOL_VERSION,
458
493
  index
package/dist/client.d.cts CHANGED
@@ -5,7 +5,7 @@ import { ReactNode } from 'react';
5
5
  import '@cmssy/types';
6
6
 
7
7
  interface EditBridgeConfig {
8
- editorOrigin: string;
8
+ editorOrigin: string | string[];
9
9
  schemas?: Record<string, BlockSchema>;
10
10
  blockMeta?: Record<string, BlockMeta>;
11
11
  }
package/dist/client.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ReactNode } from 'react';
5
5
  import '@cmssy/types';
6
6
 
7
7
  interface EditBridgeConfig {
8
- editorOrigin: string;
8
+ editorOrigin: string | string[];
9
9
  schemas?: Record<string, BlockSchema>;
10
10
  blockMeta?: Record<string, BlockMeta>;
11
11
  }
package/dist/client.js CHANGED
@@ -42,22 +42,37 @@ var PROTOCOL_VERSION = 2;
42
42
 
43
43
  // src/bridge/messages.ts
44
44
  function normalizeOrigin(origin) {
45
- if (origin === "*") return "*";
45
+ const trimmed = origin.trim();
46
+ if (trimmed === "*") return "*";
46
47
  try {
47
- return new URL(origin).origin;
48
+ return new URL(trimmed).origin;
48
49
  } catch {
49
- return origin;
50
+ return trimmed;
50
51
  }
51
52
  }
52
53
  function postToEditor(target, editorOrigin, message) {
53
54
  target.postMessage(message, normalizeOrigin(editorOrigin));
54
55
  }
56
+ function isOriginAllowed(origin, allowed) {
57
+ const list = Array.isArray(allowed) ? allowed : [allowed];
58
+ const actual = normalizeOrigin(origin);
59
+ return list.some((candidate) => {
60
+ const expected = normalizeOrigin(candidate);
61
+ return expected === "*" || expected === actual;
62
+ });
63
+ }
64
+ function resolveInitialTarget(editorOrigin) {
65
+ const list = (Array.isArray(editorOrigin) ? editorOrigin : [editorOrigin]).map((origin) => normalizeOrigin(origin));
66
+ if (list.includes("*")) return "*";
67
+ if (list.length === 1) return list[0];
68
+ const referrerOrigin = typeof document !== "undefined" && document.referrer ? normalizeOrigin(document.referrer) : "";
69
+ return list.find((origin) => origin === referrerOrigin) ?? list[0];
70
+ }
55
71
  function isObject(value) {
56
72
  return typeof value === "object" && value !== null && !Array.isArray(value);
57
73
  }
58
74
  function parseEditorMessage(data, origin, expectedOrigin) {
59
- const expected = normalizeOrigin(expectedOrigin);
60
- if (expected !== "*" && origin !== expected) return null;
75
+ if (!isOriginAllowed(origin, expectedOrigin)) return null;
61
76
  if (!isObject(data)) return null;
62
77
  switch (data.type) {
63
78
  case "cmssy:select":
@@ -128,6 +143,18 @@ function collectRects() {
128
143
  }
129
144
  return rects;
130
145
  }
146
+ function findBlockEl(blockId) {
147
+ if (typeof document === "undefined") return null;
148
+ const escaped = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(blockId) : blockId.replace(/["\\]/g, "\\$&");
149
+ try {
150
+ return document.querySelector(`[data-block-id="${escaped}"]`);
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+ function prefersReducedMotion() {
156
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
157
+ }
131
158
  function collectLayoutBlocks(rects, pageIds) {
132
159
  const out = [];
133
160
  if (typeof document === "undefined") return out;
@@ -172,7 +199,9 @@ function useEditBridge(page, config) {
172
199
  useEffect(() => {
173
200
  if (typeof window === "undefined" || window.parent === window) return;
174
201
  const { editorOrigin } = config;
175
- if (editorOrigin === "*" && typeof console !== "undefined") {
202
+ let postTarget = resolveInitialTarget(editorOrigin);
203
+ const isWildcard = Array.isArray(editorOrigin) ? editorOrigin.includes("*") : editorOrigin === "*";
204
+ if (isWildcard && typeof console !== "undefined") {
176
205
  console.warn(
177
206
  "[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
178
207
  );
@@ -181,7 +210,7 @@ function useEditBridge(page, config) {
181
210
  try {
182
211
  const rects = collectRects();
183
212
  const pageIds = new Set(blocks.map((b) => b.id));
184
- postToEditor(window.parent, editorOrigin, {
213
+ postToEditor(window.parent, postTarget, {
185
214
  type: "cmssy:ready",
186
215
  protocolVersion: PROTOCOL_VERSION,
187
216
  blocks: [
@@ -209,6 +238,7 @@ function useEditBridge(page, config) {
209
238
  editorOrigin
210
239
  );
211
240
  if (!message) return;
241
+ postTarget = event.origin;
212
242
  if (message.type === "cmssy:patch") {
213
243
  if (message.layoutPosition !== void 0) return;
214
244
  setPatches((prev) => ({
@@ -233,6 +263,10 @@ function useEditBridge(page, config) {
233
263
  } else if (message.type === "cmssy:select") {
234
264
  setSelected(message.blockId);
235
265
  selectedIdRef.current = message.blockId;
266
+ findBlockEl(message.blockId)?.scrollIntoView({
267
+ behavior: prefersReducedMotion() ? "auto" : "smooth",
268
+ block: "nearest"
269
+ });
236
270
  } else if (message.type === "cmssy:insert") {
237
271
  setInserted((prev) => {
238
272
  const next = prev.filter((b) => b.blockId !== message.blockId);
@@ -266,7 +300,7 @@ function useEditBridge(page, config) {
266
300
  const r = el.getBoundingClientRect();
267
301
  const layoutPosition = el.getAttribute("data-layout-position");
268
302
  try {
269
- postToEditor(window.parent, editorOrigin, {
303
+ postToEditor(window.parent, postTarget, {
270
304
  type: "cmssy:click",
271
305
  blockId: id,
272
306
  rect: { x: r.x, y: r.y, width: r.width, height: r.height },
@@ -285,13 +319,11 @@ function useEditBridge(page, config) {
285
319
  boundsRaf = 0;
286
320
  const id = selectedIdRef.current;
287
321
  if (!id) return;
288
- const el = document.querySelector(
289
- `[data-block-id="${id.replace(/["\\]/g, "\\$&")}"]`
290
- );
322
+ const el = findBlockEl(id);
291
323
  if (!el) return;
292
324
  const r = el.getBoundingClientRect();
293
325
  try {
294
- postToEditor(window.parent, editorOrigin, {
326
+ postToEditor(window.parent, postTarget, {
295
327
  type: "cmssy:bounds",
296
328
  blockId: id,
297
329
  rect: { x: r.x, y: r.y, width: r.width, height: r.height }
@@ -381,7 +413,9 @@ function useDragAgent(config) {
381
413
  useEffect(() => {
382
414
  if (typeof window === "undefined" || window.parent === window) return;
383
415
  const { editorOrigin } = config;
384
- if (editorOrigin === "*" && typeof console !== "undefined") {
416
+ let postTarget = resolveInitialTarget(editorOrigin);
417
+ const isWildcard = Array.isArray(editorOrigin) ? editorOrigin.includes("*") : editorOrigin === "*";
418
+ if (isWildcard && typeof console !== "undefined") {
385
419
  console.warn(
386
420
  "[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
387
421
  );
@@ -417,7 +451,7 @@ function useDragAgent(config) {
417
451
  updateDropY(null);
418
452
  resolver.invalidate();
419
453
  try {
420
- postToEditor(window.parent, editorOrigin, {
454
+ postToEditor(window.parent, postTarget, {
421
455
  type: "cmssy:move",
422
456
  protocolVersion: PROTOCOL_VERSION,
423
457
  blockId,
@@ -439,6 +473,7 @@ function useDragAgent(config) {
439
473
  editorOrigin
440
474
  );
441
475
  if (!message) return;
476
+ postTarget = event.origin;
442
477
  if (message.type === "cmssy:drag-over") {
443
478
  const edge = 64;
444
479
  const step = 20;
@@ -450,7 +485,7 @@ function useDragAgent(config) {
450
485
  const { index, y } = resolver.resolve(message.y);
451
486
  updateDropY(y);
452
487
  try {
453
- postToEditor(window.parent, editorOrigin, {
488
+ postToEditor(window.parent, postTarget, {
454
489
  type: "cmssy:drag-index",
455
490
  protocolVersion: PROTOCOL_VERSION,
456
491
  index
package/dist/index.cjs CHANGED
@@ -239,22 +239,30 @@ function isProtocolCompatible(version) {
239
239
 
240
240
  // src/bridge/messages.ts
241
241
  function normalizeOrigin(origin) {
242
- if (origin === "*") return "*";
242
+ const trimmed = origin.trim();
243
+ if (trimmed === "*") return "*";
243
244
  try {
244
- return new URL(origin).origin;
245
+ return new URL(trimmed).origin;
245
246
  } catch {
246
- return origin;
247
+ return trimmed;
247
248
  }
248
249
  }
249
250
  function postToEditor(target, editorOrigin, message) {
250
251
  target.postMessage(message, normalizeOrigin(editorOrigin));
251
252
  }
253
+ function isOriginAllowed(origin, allowed) {
254
+ const list = Array.isArray(allowed) ? allowed : [allowed];
255
+ const actual = normalizeOrigin(origin);
256
+ return list.some((candidate) => {
257
+ const expected = normalizeOrigin(candidate);
258
+ return expected === "*" || expected === actual;
259
+ });
260
+ }
252
261
  function isObject(value) {
253
262
  return typeof value === "object" && value !== null && !Array.isArray(value);
254
263
  }
255
264
  function parseEditorMessage(data, origin, expectedOrigin) {
256
- const expected = normalizeOrigin(expectedOrigin);
257
- if (expected !== "*" && origin !== expected) return null;
265
+ if (!isOriginAllowed(origin, expectedOrigin)) return null;
258
266
  if (!isObject(data)) return null;
259
267
  switch (data.type) {
260
268
  case "cmssy:select":
@@ -749,23 +757,23 @@ function createCmssyClient(input) {
749
757
  return {
750
758
  config,
751
759
  resolveWorkspaceId: resolveWorkspaceId2,
752
- query(document, variables = {}, options) {
760
+ query(document2, variables = {}, options) {
753
761
  return graphqlRequest(
754
762
  config,
755
- document,
763
+ document2,
756
764
  variables,
757
765
  options,
758
766
  "graphql operation"
759
767
  );
760
768
  },
761
- async queryScoped(document, variables = {}, options = {}) {
769
+ async queryScoped(document2, variables = {}, options = {}) {
762
770
  const { workspaceId: provided, headers, ...rest } = options;
763
771
  const workspaceId = provided ?? await resolveWorkspaceId2({ ...rest, headers });
764
772
  const hasWorkspaceId = variables.workspaceId !== void 0 && variables.workspaceId !== null;
765
- const scopedVariables = /\$workspaceId\b/.test(document) && !hasWorkspaceId ? { ...variables, workspaceId } : variables;
773
+ const scopedVariables = /\$workspaceId\b/.test(document2) && !hasWorkspaceId ? { ...variables, workspaceId } : variables;
766
774
  return graphqlRequest(
767
775
  config,
768
- document,
776
+ document2,
769
777
  scopedVariables,
770
778
  { ...rest, headers: { ...headers, "x-workspace-id": workspaceId } },
771
779
  "graphql operation"
package/dist/index.d.cts CHANGED
@@ -68,7 +68,7 @@ interface PostTarget {
68
68
  }
69
69
  declare function normalizeOrigin(origin: string): string;
70
70
  declare function postToEditor(target: PostTarget, editorOrigin: string, message: AppToEditorMessage): void;
71
- declare function parseEditorMessage(data: unknown, origin: string, expectedOrigin: string): EditorToAppMessage | null;
71
+ declare function parseEditorMessage(data: unknown, origin: string, expectedOrigin: string | string[]): EditorToAppMessage | null;
72
72
 
73
73
  declare function getBlockContentForLanguage(content: unknown, locale: string, defaultLocale?: string, availableLocales?: string[]): Record<string, unknown>;
74
74
 
package/dist/index.d.ts CHANGED
@@ -68,7 +68,7 @@ interface PostTarget {
68
68
  }
69
69
  declare function normalizeOrigin(origin: string): string;
70
70
  declare function postToEditor(target: PostTarget, editorOrigin: string, message: AppToEditorMessage): void;
71
- declare function parseEditorMessage(data: unknown, origin: string, expectedOrigin: string): EditorToAppMessage | null;
71
+ declare function parseEditorMessage(data: unknown, origin: string, expectedOrigin: string | string[]): EditorToAppMessage | null;
72
72
 
73
73
  declare function getBlockContentForLanguage(content: unknown, locale: string, defaultLocale?: string, availableLocales?: string[]): Record<string, unknown>;
74
74
 
package/dist/index.js CHANGED
@@ -237,22 +237,30 @@ function isProtocolCompatible(version) {
237
237
 
238
238
  // src/bridge/messages.ts
239
239
  function normalizeOrigin(origin) {
240
- if (origin === "*") return "*";
240
+ const trimmed = origin.trim();
241
+ if (trimmed === "*") return "*";
241
242
  try {
242
- return new URL(origin).origin;
243
+ return new URL(trimmed).origin;
243
244
  } catch {
244
- return origin;
245
+ return trimmed;
245
246
  }
246
247
  }
247
248
  function postToEditor(target, editorOrigin, message) {
248
249
  target.postMessage(message, normalizeOrigin(editorOrigin));
249
250
  }
251
+ function isOriginAllowed(origin, allowed) {
252
+ const list = Array.isArray(allowed) ? allowed : [allowed];
253
+ const actual = normalizeOrigin(origin);
254
+ return list.some((candidate) => {
255
+ const expected = normalizeOrigin(candidate);
256
+ return expected === "*" || expected === actual;
257
+ });
258
+ }
250
259
  function isObject(value) {
251
260
  return typeof value === "object" && value !== null && !Array.isArray(value);
252
261
  }
253
262
  function parseEditorMessage(data, origin, expectedOrigin) {
254
- const expected = normalizeOrigin(expectedOrigin);
255
- if (expected !== "*" && origin !== expected) return null;
263
+ if (!isOriginAllowed(origin, expectedOrigin)) return null;
256
264
  if (!isObject(data)) return null;
257
265
  switch (data.type) {
258
266
  case "cmssy:select":
@@ -747,23 +755,23 @@ function createCmssyClient(input) {
747
755
  return {
748
756
  config,
749
757
  resolveWorkspaceId: resolveWorkspaceId2,
750
- query(document, variables = {}, options) {
758
+ query(document2, variables = {}, options) {
751
759
  return graphqlRequest(
752
760
  config,
753
- document,
761
+ document2,
754
762
  variables,
755
763
  options,
756
764
  "graphql operation"
757
765
  );
758
766
  },
759
- async queryScoped(document, variables = {}, options = {}) {
767
+ async queryScoped(document2, variables = {}, options = {}) {
760
768
  const { workspaceId: provided, headers, ...rest } = options;
761
769
  const workspaceId = provided ?? await resolveWorkspaceId2({ ...rest, headers });
762
770
  const hasWorkspaceId = variables.workspaceId !== void 0 && variables.workspaceId !== null;
763
- const scopedVariables = /\$workspaceId\b/.test(document) && !hasWorkspaceId ? { ...variables, workspaceId } : variables;
771
+ const scopedVariables = /\$workspaceId\b/.test(document2) && !hasWorkspaceId ? { ...variables, workspaceId } : variables;
764
772
  return graphqlRequest(
765
773
  config,
766
- document,
774
+ document2,
767
775
  scopedVariables,
768
776
  { ...rest, headers: { ...headers, "x-workspace-id": workspaceId } },
769
777
  "graphql operation"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/react",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "React blocks, renderers, data client and editor bridge for cmssy headless sites",
5
5
  "keywords": [
6
6
  "cmssy",