@cmssy/react 9.2.0 → 9.4.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,90 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/components/block-error.ts
8
+ function blockErrorMessage(err) {
9
+ return err instanceof Error ? err.message : String(err);
10
+ }
11
+ var SOURCE_LABELS = {
12
+ loader: "loader failed",
13
+ render: "render failed",
14
+ unregistered: "type not registered"
15
+ };
16
+ var cardStyle = {
17
+ boxSizing: "border-box",
18
+ margin: "8px 0",
19
+ padding: "12px 16px",
20
+ border: "1px dashed #ef4444",
21
+ borderRadius: "8px",
22
+ background: "rgba(239, 68, 68, 0.08)",
23
+ color: "#ef4444",
24
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
25
+ fontSize: "13px",
26
+ lineHeight: 1.5,
27
+ textAlign: "left"
28
+ };
29
+ var headingStyle = {
30
+ fontWeight: 700,
31
+ marginBottom: "4px"
32
+ };
33
+ var metaStyle = {
34
+ opacity: 0.8,
35
+ marginBottom: "4px"
36
+ };
37
+ var messageStyle = {
38
+ whiteSpace: "pre-wrap",
39
+ overflowWrap: "anywhere"
40
+ };
41
+ function BlockErrorCard({
42
+ blockType,
43
+ blockId,
44
+ error
45
+ }) {
46
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", "data-cmssy-block-error": error.source, style: cardStyle, children: [
47
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headingStyle, children: [
48
+ 'Block "',
49
+ blockType,
50
+ '" - ',
51
+ SOURCE_LABELS[error.source]
52
+ ] }),
53
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: metaStyle, children: [
54
+ "id: ",
55
+ blockId
56
+ ] }),
57
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: messageStyle, children: error.message })
58
+ ] });
59
+ }
60
+ var BlockErrorBoundary = class extends react.Component {
61
+ state = { failed: false, error: void 0 };
62
+ static getDerivedStateFromError(error) {
63
+ return { failed: true, error };
64
+ }
65
+ componentDidCatch(error) {
66
+ if (typeof console !== "undefined") {
67
+ console.error(
68
+ `[cmssy] block "${this.props.blockType}" (${this.props.blockId}) failed to render`,
69
+ error
70
+ );
71
+ }
72
+ }
73
+ render() {
74
+ if (!this.state.failed) return this.props.children;
75
+ if (!this.props.editMode) return null;
76
+ return /* @__PURE__ */ jsxRuntime.jsx(
77
+ BlockErrorCard,
78
+ {
79
+ blockType: this.props.blockType,
80
+ blockId: this.props.blockId,
81
+ error: {
82
+ source: "render",
83
+ message: blockErrorMessage(this.state.error)
84
+ }
85
+ }
86
+ );
87
+ }
88
+ };
89
+
90
+ exports.BlockErrorBoundary = BlockErrorBoundary;
@@ -0,0 +1,22 @@
1
+ import * as react from 'react';
2
+ import { Component, ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+
5
+ interface BlockErrorBoundaryProps {
6
+ blockType: string;
7
+ blockId: string;
8
+ editMode?: boolean;
9
+ children?: ReactNode;
10
+ }
11
+ interface BlockErrorBoundaryState {
12
+ failed: boolean;
13
+ error: unknown;
14
+ }
15
+ declare class BlockErrorBoundary extends Component<BlockErrorBoundaryProps, BlockErrorBoundaryState> {
16
+ state: BlockErrorBoundaryState;
17
+ static getDerivedStateFromError(error: unknown): BlockErrorBoundaryState;
18
+ componentDidCatch(error: unknown): void;
19
+ render(): string | number | bigint | boolean | react_jsx_runtime.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
20
+ }
21
+
22
+ export { BlockErrorBoundary, type BlockErrorBoundaryProps };
@@ -0,0 +1,22 @@
1
+ import * as react from 'react';
2
+ import { Component, ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+
5
+ interface BlockErrorBoundaryProps {
6
+ blockType: string;
7
+ blockId: string;
8
+ editMode?: boolean;
9
+ children?: ReactNode;
10
+ }
11
+ interface BlockErrorBoundaryState {
12
+ failed: boolean;
13
+ error: unknown;
14
+ }
15
+ declare class BlockErrorBoundary extends Component<BlockErrorBoundaryProps, BlockErrorBoundaryState> {
16
+ state: BlockErrorBoundaryState;
17
+ static getDerivedStateFromError(error: unknown): BlockErrorBoundaryState;
18
+ componentDidCatch(error: unknown): void;
19
+ render(): string | number | bigint | boolean | react_jsx_runtime.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
20
+ }
21
+
22
+ export { BlockErrorBoundary, type BlockErrorBoundaryProps };
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ import { Component } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ // src/components/block-error.ts
6
+ function blockErrorMessage(err) {
7
+ return err instanceof Error ? err.message : String(err);
8
+ }
9
+ var SOURCE_LABELS = {
10
+ loader: "loader failed",
11
+ render: "render failed",
12
+ unregistered: "type not registered"
13
+ };
14
+ var cardStyle = {
15
+ boxSizing: "border-box",
16
+ margin: "8px 0",
17
+ padding: "12px 16px",
18
+ border: "1px dashed #ef4444",
19
+ borderRadius: "8px",
20
+ background: "rgba(239, 68, 68, 0.08)",
21
+ color: "#ef4444",
22
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
23
+ fontSize: "13px",
24
+ lineHeight: 1.5,
25
+ textAlign: "left"
26
+ };
27
+ var headingStyle = {
28
+ fontWeight: 700,
29
+ marginBottom: "4px"
30
+ };
31
+ var metaStyle = {
32
+ opacity: 0.8,
33
+ marginBottom: "4px"
34
+ };
35
+ var messageStyle = {
36
+ whiteSpace: "pre-wrap",
37
+ overflowWrap: "anywhere"
38
+ };
39
+ function BlockErrorCard({
40
+ blockType,
41
+ blockId,
42
+ error
43
+ }) {
44
+ return /* @__PURE__ */ jsxs("div", { role: "alert", "data-cmssy-block-error": error.source, style: cardStyle, children: [
45
+ /* @__PURE__ */ jsxs("div", { style: headingStyle, children: [
46
+ 'Block "',
47
+ blockType,
48
+ '" - ',
49
+ SOURCE_LABELS[error.source]
50
+ ] }),
51
+ /* @__PURE__ */ jsxs("div", { style: metaStyle, children: [
52
+ "id: ",
53
+ blockId
54
+ ] }),
55
+ /* @__PURE__ */ jsx("div", { style: messageStyle, children: error.message })
56
+ ] });
57
+ }
58
+ var BlockErrorBoundary = class extends Component {
59
+ state = { failed: false, error: void 0 };
60
+ static getDerivedStateFromError(error) {
61
+ return { failed: true, error };
62
+ }
63
+ componentDidCatch(error) {
64
+ if (typeof console !== "undefined") {
65
+ console.error(
66
+ `[cmssy] block "${this.props.blockType}" (${this.props.blockId}) failed to render`,
67
+ error
68
+ );
69
+ }
70
+ }
71
+ render() {
72
+ if (!this.state.failed) return this.props.children;
73
+ if (!this.props.editMode) return null;
74
+ return /* @__PURE__ */ jsx(
75
+ BlockErrorCard,
76
+ {
77
+ blockType: this.props.blockType,
78
+ blockId: this.props.blockId,
79
+ error: {
80
+ source: "render",
81
+ message: blockErrorMessage(this.state.error)
82
+ }
83
+ }
84
+ );
85
+ }
86
+ };
87
+
88
+ export { BlockErrorBoundary };
package/dist/client.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  var react = require('react');
5
5
  var core = require('@cmssy/core');
6
+ var blockErrorBoundary = require('@cmssy/react/block-error-boundary');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
 
8
9
  // src/registry.ts
@@ -115,6 +116,15 @@ function useEditBridge(page, config) {
115
116
  "[cmssy] editorOrigin '*' disables origin checks - dev only, do not use in production"
116
117
  );
117
118
  }
119
+ const postSafe = (message) => {
120
+ try {
121
+ core.postToEditor(window.parent, postTarget, message);
122
+ } catch (error) {
123
+ if (typeof console !== "undefined") {
124
+ console.debug("[cmssy] post to editor failed", message.type, error);
125
+ }
126
+ }
127
+ };
118
128
  const sendReady = () => {
119
129
  try {
120
130
  const rects = collectRects();
@@ -203,20 +213,24 @@ function useEditBridge(page, config) {
203
213
  const target = event.target;
204
214
  const el = target?.closest?.("[data-block-id]");
205
215
  const id = el?.getAttribute("data-block-id");
206
- if (!id || !el) return;
216
+ if (!id || !el) {
217
+ if (!selectedIdRef.current) return;
218
+ if (target?.closest?.("a[href],button")) return;
219
+ selectedIdRef.current = null;
220
+ setSelected(null);
221
+ postSafe({ type: "cmssy:deselect" });
222
+ return;
223
+ }
207
224
  selectedIdRef.current = id;
208
225
  if (target?.closest?.("a[href]")) event.preventDefault();
209
226
  const r = el.getBoundingClientRect();
210
227
  const layoutPosition = el.getAttribute("data-layout-position");
211
- try {
212
- core.postToEditor(window.parent, postTarget, {
213
- type: "cmssy:click",
214
- blockId: id,
215
- rect: { x: r.x, y: r.y, width: r.width, height: r.height },
216
- ...layoutPosition !== null ? { layoutPosition } : {}
217
- });
218
- } catch {
219
- }
228
+ postSafe({
229
+ type: "cmssy:click",
230
+ blockId: id,
231
+ rect: { x: r.x, y: r.y, width: r.width, height: r.height },
232
+ ...layoutPosition !== null ? { layoutPosition } : {}
233
+ });
220
234
  };
221
235
  let boundsRaf = 0;
222
236
  let boundsPending = false;
@@ -231,14 +245,11 @@ function useEditBridge(page, config) {
231
245
  const el = findBlockEl(id);
232
246
  if (!el) return;
233
247
  const r = el.getBoundingClientRect();
234
- try {
235
- core.postToEditor(window.parent, postTarget, {
236
- type: "cmssy:bounds",
237
- blockId: id,
238
- rect: { x: r.x, y: r.y, width: r.width, height: r.height }
239
- });
240
- } catch {
241
- }
248
+ postSafe({
249
+ type: "cmssy:bounds",
250
+ blockId: id,
251
+ rect: { x: r.x, y: r.y, width: r.width, height: r.height }
252
+ });
242
253
  });
243
254
  };
244
255
  window.addEventListener("message", handler);
@@ -421,13 +432,85 @@ function useDragAgent(config) {
421
432
  }, [config.editorOrigin]);
422
433
  return { dropY };
423
434
  }
435
+
436
+ // src/components/block-error.ts
437
+ var BLOCK_ERROR_KEY = "__cmssyBlockError";
438
+ var SOURCES = [
439
+ "loader",
440
+ "render",
441
+ "unregistered"
442
+ ];
443
+ function unregisteredBlockError(type) {
444
+ return {
445
+ source: "unregistered",
446
+ message: `Block type "${type}" is not registered in this site's blocks array.`
447
+ };
448
+ }
449
+ function readBlockError(value) {
450
+ if (typeof value !== "object" || value === null) return void 0;
451
+ const marked = value[BLOCK_ERROR_KEY];
452
+ if (typeof marked !== "object" || marked === null) return void 0;
453
+ const { message, source } = marked;
454
+ if (typeof message !== "string") return void 0;
455
+ if (!SOURCES.includes(source)) return void 0;
456
+ return { message, source };
457
+ }
458
+ var SOURCE_LABELS = {
459
+ loader: "loader failed",
460
+ render: "render failed",
461
+ unregistered: "type not registered"
462
+ };
463
+ var cardStyle = {
464
+ boxSizing: "border-box",
465
+ margin: "8px 0",
466
+ padding: "12px 16px",
467
+ border: "1px dashed #ef4444",
468
+ borderRadius: "8px",
469
+ background: "rgba(239, 68, 68, 0.08)",
470
+ color: "#ef4444",
471
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace",
472
+ fontSize: "13px",
473
+ lineHeight: 1.5,
474
+ textAlign: "left"
475
+ };
476
+ var headingStyle = {
477
+ fontWeight: 700,
478
+ marginBottom: "4px"
479
+ };
480
+ var metaStyle = {
481
+ opacity: 0.8,
482
+ marginBottom: "4px"
483
+ };
484
+ var messageStyle = {
485
+ whiteSpace: "pre-wrap",
486
+ overflowWrap: "anywhere"
487
+ };
488
+ function BlockErrorCard({
489
+ blockType,
490
+ blockId,
491
+ error
492
+ }) {
493
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", "data-cmssy-block-error": error.source, style: cardStyle, children: [
494
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headingStyle, children: [
495
+ 'Block "',
496
+ blockType,
497
+ '" - ',
498
+ SOURCE_LABELS[error.source]
499
+ ] }),
500
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: metaStyle, children: [
501
+ "id: ",
502
+ blockId
503
+ ] }),
504
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: messageStyle, children: error.message })
505
+ ] });
506
+ }
424
507
  var WARN_CAP = 256;
425
508
  var warned = /* @__PURE__ */ new Set();
426
509
  function UnknownBlock({ type }) {
427
510
  if (typeof window !== "undefined" && !warned.has(type)) {
428
511
  if (warned.size >= WARN_CAP) warned.clear();
429
512
  warned.add(type);
430
- console.warn(`[cmssy] no component registered for block type "${type}"`);
513
+ console.error(`[cmssy] no component registered for block type "${type}"`);
431
514
  }
432
515
  return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cmssy-unknown-block": type });
433
516
  }
@@ -440,32 +523,71 @@ function CmssyBlock({
440
523
  patchedStyle,
441
524
  patchedAdvanced,
442
525
  editable,
526
+ editMode,
443
527
  layoutPosition,
444
528
  context,
445
529
  data
446
530
  }) {
531
+ const inEditMode = editMode ?? editable ?? false;
447
532
  const Component = Object.hasOwn(blockMap, block.type) ? blockMap[block.type] : void 0;
448
- const base = core.getBlockContentForLanguage(block.content, locale, defaultLocale);
449
- const content = patchedContent ? { ...base, ...patchedContent } : base;
450
- const style = patchedStyle ? { ...core.asBucket(block.style), ...patchedStyle } : core.asBucket(block.style);
451
- const advanced = patchedAdvanced ? { ...core.asBucket(block.advanced), ...patchedAdvanced } : core.asBucket(block.advanced);
452
- return /* @__PURE__ */ jsxRuntime.jsx(
533
+ const blockError = readBlockError(data);
534
+ const wrap = (children, hidden = false) => /* @__PURE__ */ jsxRuntime.jsx(
453
535
  "div",
454
536
  {
455
537
  "data-block-id": block.id,
456
538
  "data-block-type": block.type,
457
539
  "data-layout-position": layoutPosition,
458
540
  draggable: editable || void 0,
459
- style: Component ? void 0 : { display: "none" },
460
- children: Component ? react.createElement(Component, {
461
- content,
462
- style,
463
- advanced,
464
- context,
465
- data
466
- }) : /* @__PURE__ */ jsxRuntime.jsx(UnknownBlock, { type: block.type })
541
+ style: hidden ? { display: "none" } : void 0,
542
+ children
467
543
  }
468
544
  );
545
+ if (!Component) {
546
+ return inEditMode ? wrap(
547
+ /* @__PURE__ */ jsxRuntime.jsx(
548
+ BlockErrorCard,
549
+ {
550
+ blockType: block.type,
551
+ blockId: block.id,
552
+ error: unregisteredBlockError(block.type)
553
+ }
554
+ )
555
+ ) : wrap(/* @__PURE__ */ jsxRuntime.jsx(UnknownBlock, { type: block.type }), true);
556
+ }
557
+ if (blockError) {
558
+ if (!inEditMode) return null;
559
+ return wrap(
560
+ /* @__PURE__ */ jsxRuntime.jsx(
561
+ BlockErrorCard,
562
+ {
563
+ blockType: block.type,
564
+ blockId: block.id,
565
+ error: blockError
566
+ }
567
+ )
568
+ );
569
+ }
570
+ const base = core.getBlockContentForLanguage(block.content, locale, defaultLocale);
571
+ const content = patchedContent ? { ...base, ...patchedContent } : base;
572
+ const style = patchedStyle ? { ...core.asBucket(block.style), ...patchedStyle } : core.asBucket(block.style);
573
+ const advanced = patchedAdvanced ? { ...core.asBucket(block.advanced), ...patchedAdvanced } : core.asBucket(block.advanced);
574
+ return wrap(
575
+ /* @__PURE__ */ jsxRuntime.jsx(
576
+ blockErrorBoundary.BlockErrorBoundary,
577
+ {
578
+ blockType: block.type,
579
+ blockId: block.id,
580
+ editMode: inEditMode,
581
+ children: react.createElement(Component, {
582
+ content,
583
+ style,
584
+ advanced,
585
+ context,
586
+ data
587
+ })
588
+ }
589
+ )
590
+ );
469
591
  }
470
592
  function CmssyEditablePage({
471
593
  page,
@@ -674,6 +796,7 @@ function CmssyEditableLayout({
674
796
  defaultLocale,
675
797
  blockMap,
676
798
  patchedContent: patches[block.id],
799
+ editMode: true,
677
800
  layoutPosition: position,
678
801
  context,
679
802
  data: data?.[block.id]