@cloudcannon/editable-regions 0.0.14 → 0.0.16

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.
@@ -9,12 +9,12 @@ export function getEditableComponentRenderers(): Record<string, ComponentRendere
9
9
  export function getEditableSnippetRenderers(): Record<string, ComponentRenderer>;
10
10
  export function realizeAPIValue(value: unknown): Promise<unknown>;
11
11
  export { _cloudcannon as CloudCannon };
12
- export type CloudCannonEditorWindow = import("@cloudcannon/javascript-api").CloudCannonEditorWindow;
13
- export type CloudCannonJavaScriptV1API = import("@cloudcannon/javascript-api").CloudCannonJavaScriptV1API;
12
+ export type CloudCannonVisualEditorWindow = import("@cloudcannon/visual-editor-api").CloudCannonVisualEditorWindow;
13
+ export type CloudCannonVisualEditorAPIV1 = import("@cloudcannon/visual-editor-api").CloudCannonVisualEditorAPIV1;
14
14
  export type ComponentRenderer = (props: any) => HTMLElement | Promise<HTMLElement>;
15
- export type ExtendedWindow = CloudCannonEditorWindow & {
15
+ export type ExtendedWindow = CloudCannonVisualEditorWindow & {
16
16
  cc_components?: Record<string, ComponentRenderer>;
17
17
  cc_snippets?: Record<string, ComponentRenderer>;
18
18
  };
19
- /** @type {CloudCannonJavaScriptV1API} */
20
- declare let _cloudcannon: CloudCannonJavaScriptV1API;
19
+ /** @type {CloudCannonVisualEditorAPIV1} */
20
+ declare let _cloudcannon: CloudCannonVisualEditorAPIV1;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @typedef {import("@cloudcannon/javascript-api").CloudCannonEditorWindow} CloudCannonEditorWindow
3
- * @typedef {import("@cloudcannon/javascript-api").CloudCannonJavaScriptV1API} CloudCannonJavaScriptV1API
2
+ * @typedef {import("@cloudcannon/visual-editor-api").CloudCannonVisualEditorWindow} CloudCannonVisualEditorWindow
3
+ * @typedef {import("@cloudcannon/visual-editor-api").CloudCannonVisualEditorAPIV1} CloudCannonVisualEditorAPIV1
4
4
  */
5
5
 
6
6
  /**
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  /**
11
- * @typedef {CloudCannonEditorWindow & {
11
+ * @typedef {CloudCannonVisualEditorWindow & {
12
12
  * cc_components?: Record<string, ComponentRenderer>;
13
13
  * cc_snippets?: Record<string, ComponentRenderer>;
14
14
  * }} ExtendedWindow
@@ -17,7 +17,7 @@
17
17
  /** @type {ExtendedWindow} */
18
18
  const extendedWindow = /** @type {any} */ (window);
19
19
 
20
- /** @type {CloudCannonJavaScriptV1API} */
20
+ /** @type {CloudCannonVisualEditorAPIV1} */
21
21
  let _cloudcannon;
22
22
 
23
23
  /**
@@ -10,10 +10,25 @@ addFrameworkRenderer({
10
10
  clientEntrypoint: "@astrojs/react/client.js",
11
11
  ssr: {
12
12
  /**
13
- * Always returns true to handle all remaining components as React.
14
- * @returns {boolean} Always true
13
+ * @param {any} Component
14
+ * @returns {boolean}
15
15
  */
16
- check: () => true,
16
+ check: (Component) => {
17
+ if (typeof Component !== "function") return false;
18
+
19
+ // React class components have render on the prototype
20
+ if (typeof Component.prototype?.render === "function") return true;
21
+
22
+ // React functional components return vnodes with $$typeof
23
+ try {
24
+ const vnode = Component({});
25
+ return (
26
+ vnode != null && typeof vnode === "object" && "$$typeof" in vnode
27
+ );
28
+ } catch {
29
+ return false;
30
+ }
31
+ },
17
32
  /**
18
33
  * Renders a React component to static markup or queues for client-side rendering.
19
34
  * @param {any} Component - The React component function
@@ -0,0 +1,65 @@
1
+ import { addFrameworkRenderer, queueForClientSideRender } from "./index.mjs";
2
+
3
+ /** @type{((component: any, args: { target: HTMLElement, props: unknown }) => void) | undefined} */
4
+ let mount;
5
+ /** @type{() => void} */
6
+ let flushSync;
7
+ try {
8
+ ({ mount, flushSync } = await import("svelte"));
9
+ } catch {
10
+ // Svelte 4 — no mount export
11
+ }
12
+
13
+ addFrameworkRenderer({
14
+ name: "@astrojs/svelte",
15
+ clientEntrypoint: "@astrojs/svelte/client.js",
16
+ ssr: {
17
+ /**
18
+ * @param {any} Component
19
+ * @returns {boolean}
20
+ */
21
+ check: (Component) => {
22
+ if (typeof Component !== "function") return false;
23
+
24
+ // Svelte 5: compiled components reference $$payload or $$renderer
25
+ const str = Component.toString();
26
+ if (
27
+ str.includes("$$payload") ||
28
+ str.includes("$$renderer") ||
29
+ str.includes("$$anchor")
30
+ ) {
31
+ return true;
32
+ }
33
+
34
+ // Svelte 4: class-based components have a render static method
35
+ if (typeof Component.render === "function") {
36
+ return true;
37
+ }
38
+
39
+ return false;
40
+ },
41
+ /**
42
+ * @param {any} Component
43
+ * @param {unknown} props
44
+ * @returns {Promise<{html: string}>}
45
+ */
46
+ renderToStaticMarkup: async (Component, props) => {
47
+ const id = queueForClientSideRender((node) => {
48
+ if (mount) {
49
+ mount(Component, {
50
+ target: /** @type{any}*/ (node),
51
+ props,
52
+ });
53
+ } else {
54
+ new Component({ target: node, props });
55
+ }
56
+
57
+ flushSync();
58
+ });
59
+
60
+ return {
61
+ html: `<div data-editable-region-csr-id=${id}></div>`,
62
+ };
63
+ },
64
+ },
65
+ });
@@ -0,0 +1,40 @@
1
+ import { addEditableComponentRenderer } from "../helpers/cloudcannon.mjs";
2
+
3
+ /** @type{((component: any, args: { target: HTMLElement, props: unknown }) => void) | undefined} */
4
+ let mount;
5
+ /** @type{() => void} */
6
+ let flushSync;
7
+ try {
8
+ ({ mount, flushSync } = await import("svelte"));
9
+ } catch {
10
+ // Svelte 4 — no mount export
11
+ }
12
+
13
+ /**
14
+ * @param {string} key
15
+ * @param {any} component
16
+ */
17
+ export const registerSvelteComponent = (key, component) => {
18
+ /**
19
+ * @param {unknown} props
20
+ * @returns
21
+ */
22
+ const wrappedComponent = (props) => {
23
+ const rootEl = document.createElement("div");
24
+
25
+ if (mount) {
26
+ mount(component, {
27
+ target: rootEl,
28
+ props,
29
+ });
30
+ } else {
31
+ new component({ target: rootEl, props });
32
+ }
33
+
34
+ flushSync();
35
+
36
+ return rootEl;
37
+ };
38
+
39
+ addEditableComponentRenderer(key, wrappedComponent);
40
+ };
@@ -1,8 +1,8 @@
1
1
  import type {
2
- CloudCannonJavaScriptV1APICollection,
3
- CloudCannonJavaScriptV1APIDataset,
4
- CloudCannonJavaScriptV1APIFile,
5
- } from "@cloudcannon/javascript-api";
2
+ CloudCannonVisualEditorAPIV1Collection,
3
+ CloudCannonVisualEditorAPIV1Dataset,
4
+ CloudCannonVisualEditorAPIV1File,
5
+ } from "@cloudcannon/visual-editor-api";
6
6
  import type EditableArrayItemComponent from "../components/editable-array-item-component.js";
7
7
  import type EditableRegionButton from "../components/ui/editable-region-button.js";
8
8
  import { isEditableElement } from "../helpers/checks.js";
@@ -27,9 +27,9 @@ function isArrayDirection(value: unknown): value is ArrayDirection {
27
27
  export default class EditableArray extends Editable {
28
28
  arrayDirection?: ArrayDirection;
29
29
  value:
30
- | CloudCannonJavaScriptV1APICollection
31
- | CloudCannonJavaScriptV1APIDataset
32
- | CloudCannonJavaScriptV1APIFile
30
+ | CloudCannonVisualEditorAPIV1Collection
31
+ | CloudCannonVisualEditorAPIV1Dataset
32
+ | CloudCannonVisualEditorAPIV1File
33
33
  | unknown[]
34
34
  | null
35
35
  | undefined = undefined;
@@ -47,7 +47,7 @@ export default class EditableArray extends Editable {
47
47
  const __base_context = {
48
48
  ...this.contextBase,
49
49
  };
50
- let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
50
+ let value: unknown[] | CloudCannonVisualEditorAPIV1File[];
51
51
  if (CloudCannon.isAPICollection(this.value)) {
52
52
  value = await this.value.items();
53
53
  } else if (CloudCannon.isAPIDataset(this.value)) {
@@ -169,7 +169,7 @@ export default class EditableArray extends Editable {
169
169
  }
170
170
 
171
171
  private async _update(partialSubtree?: ChildNode | null): Promise<void> {
172
- let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
172
+ let value: unknown[] | CloudCannonVisualEditorAPIV1File[];
173
173
  const __base_context = {
174
174
  ...this.contextBase,
175
175
  };
@@ -1,4 +1,4 @@
1
- import type { CloudCannonJavaScriptV1APIFile } from "@cloudcannon/javascript-api";
1
+ import type { CloudCannonVisualEditorAPIV1File } from "@cloudcannon/visual-editor-api";
2
2
  import { html as beautifyHtml } from "js-beautify";
3
3
  import { CloudCannon } from "../helpers/cloudcannon.mjs";
4
4
  import EditableText from "./editable-text.js";
@@ -25,7 +25,7 @@ const HTML_VOID_ELEMENT: Record<string, boolean> = {
25
25
  };
26
26
 
27
27
  export default class EditableSource extends EditableText {
28
- file?: CloudCannonJavaScriptV1APIFile;
28
+ file?: CloudCannonVisualEditorAPIV1File;
29
29
  format = {
30
30
  leading: "",
31
31
  trailing: "",
@@ -247,6 +247,12 @@ export default class EditableSource extends EditableText {
247
247
 
248
248
  onChange(value?: string | null) {
249
249
  this.file?.get().then((source) => {
250
+ if (!source) {
251
+ throw new Error(
252
+ "Source editable region tried to update a non-existent file",
253
+ );
254
+ }
255
+
250
256
  value = beautifyHtml(value ?? "", {
251
257
  indent_char: this.format.indentChar,
252
258
  indent_size: this.format.indentSize,
package/nodes/editable.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type {
2
- CloudCannonJavaScriptV1APICollection,
3
- CloudCannonJavaScriptV1APIDataset,
4
- CloudCannonJavaScriptV1APIFile,
5
- } from "@cloudcannon/javascript-api";
2
+ CloudCannonVisualEditorAPIV1Collection,
3
+ CloudCannonVisualEditorAPIV1Dataset,
4
+ CloudCannonVisualEditorAPIV1File,
5
+ } from "@cloudcannon/visual-editor-api";
6
6
  import { hasEditable } from "../helpers/checks";
7
7
  import { apiLoadedPromise, CloudCannon } from "../helpers/cloudcannon.mjs";
8
8
 
@@ -22,9 +22,9 @@ export interface EditableContext {
22
22
  fullPath?: string;
23
23
  filePath?: string;
24
24
  isContent?: boolean;
25
- file?: CloudCannonJavaScriptV1APIFile;
26
- collection?: CloudCannonJavaScriptV1APICollection;
27
- dataset?: CloudCannonJavaScriptV1APIDataset;
25
+ file?: CloudCannonVisualEditorAPIV1File;
26
+ collection?: CloudCannonVisualEditorAPIV1Collection;
27
+ dataset?: CloudCannonVisualEditorAPIV1Dataset;
28
28
  }
29
29
 
30
30
  export interface DOMListener {
@@ -35,9 +35,9 @@ export interface DOMListener {
35
35
  export interface APIListener {
36
36
  fn: (e: any) => void;
37
37
  obj:
38
- | CloudCannonJavaScriptV1APIFile
39
- | CloudCannonJavaScriptV1APICollection
40
- | CloudCannonJavaScriptV1APIDataset;
38
+ | CloudCannonVisualEditorAPIV1File
39
+ | CloudCannonVisualEditorAPIV1Collection
40
+ | CloudCannonVisualEditorAPIV1Dataset;
41
41
  }
42
42
 
43
43
  export default class Editable {
@@ -598,7 +598,7 @@ export default class Editable {
598
598
  options.source,
599
599
  );
600
600
 
601
- let filePromise: Promise<CloudCannonJavaScriptV1APIFile | undefined>;
601
+ let filePromise: Promise<CloudCannonVisualEditorAPIV1File | undefined>;
602
602
  if (!file) {
603
603
  if (collection && source) {
604
604
  const parts = source.split(".");
@@ -734,9 +734,9 @@ export default class Editable {
734
734
  }
735
735
 
736
736
  parseSource(source?: string) {
737
- let collection: CloudCannonJavaScriptV1APICollection | undefined;
738
- let file: CloudCannonJavaScriptV1APIFile | undefined;
739
- let dataset: CloudCannonJavaScriptV1APIDataset | undefined;
737
+ let collection: CloudCannonVisualEditorAPIV1Collection | undefined;
738
+ let file: CloudCannonVisualEditorAPIV1File | undefined;
739
+ let dataset: CloudCannonVisualEditorAPIV1Dataset | undefined;
740
740
  let absolute = false;
741
741
  let currentFile = false;
742
742
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudcannon/editable-regions",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "type": "module",
5
5
  "description": "Visual Editing for the CloudCannon CMS.",
6
6
  "keywords": [
@@ -49,25 +49,33 @@
49
49
  "types": "./types/react.d.ts",
50
50
  "default": "./integrations/react.mjs"
51
51
  },
52
+ "./svelte": {
53
+ "types": "./types/svelte.d.ts",
54
+ "default": "./integrations/svelte.mjs"
55
+ },
56
+ "./astro-svelte-renderer": {
57
+ "types": "./types/astro.d.ts",
58
+ "default": "./integrations/astro/svelte-renderer.mjs"
59
+ },
52
60
  "./liquid": "./integrations/liquid/index.mjs",
53
61
  "./eleventy": "./integrations/eleventy.mjs",
54
62
  "./internal/components": "./components/index.js",
55
63
  "./internal/styles": "./styles/index.js"
56
64
  },
57
65
  "devDependencies": {
58
- "@biomejs/biome": "2.4.10",
59
- "@cloudcannon/javascript-api": "0.0.14",
66
+ "@biomejs/biome": "2.5.0",
67
+ "@cloudcannon/visual-editor-api": "0.0.18",
60
68
  "@sindresorhus/slugify": "3.0.0",
61
69
  "@types/js-beautify": "1.14.3",
62
- "@types/node": "25.5.0",
63
- "@types/react": "19.2.14",
70
+ "@types/node": "25.9.4",
71
+ "@types/react": "19.2.17",
64
72
  "@types/react-dom": "19.2.3",
65
- "astro": "6.1.2",
73
+ "astro": "6.4.8",
66
74
  "js-beautify": "1.15.4",
67
- "liquidjs": "10.25.5",
68
- "typescript": "6.0.2"
75
+ "liquidjs": "10.27.0",
76
+ "typescript": "6.0.3"
69
77
  },
70
78
  "dependencies": {
71
- "esbuild": "0.28.0"
79
+ "esbuild": "0.28.1"
72
80
  }
73
81
  }
package/types/astro.d.ts CHANGED
@@ -17,3 +17,7 @@ declare module "@cloudcannon/editable-regions/astro" {
17
17
  declare module "@cloudcannon/editable-regions/astro-react-renderer" {
18
18
  // Side-effect only module that registers React renderer
19
19
  }
20
+
21
+ declare module "@cloudcannon/editable-regions/astro-svelte-renderer" {
22
+ // Side-effect only module that registers Svelte renderer
23
+ }
@@ -3,9 +3,9 @@ export {};
3
3
  declare global {
4
4
  var ENV_CLIENT: boolean;
5
5
  var inEditorMode: boolean;
6
- var CloudCannonAPI: CloudCannonJavascriptApiRouter | undefined;
6
+ var CloudCannonAPI: CloudCannonVisualEditorAPIRouter | undefined;
7
7
  var CloudCannon:
8
- | CloudCannonJavaScriptV0API
9
- | CloudCannonJavaScriptV1API
8
+ | CloudCannonVisualEditorAPIV0
9
+ | CloudCannonVisualEditorAPIV1
10
10
  | undefined;
11
11
  }
@@ -10,3 +10,11 @@ declare module "astro/runtime/server/index.js" {
10
10
  declare module "react-dom/server.browser" {
11
11
  export function renderToStaticMarkup(element: any): string;
12
12
  }
13
+ declare module "svelte" {
14
+ export function mount(
15
+ component: any,
16
+ args: { target: HTMLElement; props: unknown },
17
+ ): void;
18
+
19
+ export function flushSync(): void;
20
+ }
@@ -0,0 +1,3 @@
1
+ /// <reference path="./cloudcannon.d.ts" />
2
+
3
+ export function registerSvelteComponent(key: string, component: unknown): void;