@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.
- package/helpers/cloudcannon.d.mts +5 -5
- package/helpers/cloudcannon.mjs +4 -4
- package/integrations/astro/react-renderer.mjs +18 -3
- package/integrations/astro/svelte-renderer.mjs +65 -0
- package/integrations/svelte.mjs +40 -0
- package/nodes/editable-array.ts +9 -9
- package/nodes/editable-source.ts +8 -2
- package/nodes/editable.ts +14 -14
- package/package.json +17 -9
- package/types/astro.d.ts +4 -0
- package/types/cloudcannon.d.ts +3 -3
- package/types/modules.d.ts +8 -0
- package/types/svelte.d.ts +3 -0
|
@@ -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
|
|
13
|
-
export type
|
|
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 =
|
|
15
|
+
export type ExtendedWindow = CloudCannonVisualEditorWindow & {
|
|
16
16
|
cc_components?: Record<string, ComponentRenderer>;
|
|
17
17
|
cc_snippets?: Record<string, ComponentRenderer>;
|
|
18
18
|
};
|
|
19
|
-
/** @type {
|
|
20
|
-
declare let _cloudcannon:
|
|
19
|
+
/** @type {CloudCannonVisualEditorAPIV1} */
|
|
20
|
+
declare let _cloudcannon: CloudCannonVisualEditorAPIV1;
|
package/helpers/cloudcannon.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("@cloudcannon/
|
|
3
|
-
* @typedef {import("@cloudcannon/
|
|
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 {
|
|
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 {
|
|
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
|
-
*
|
|
14
|
-
* @returns {boolean}
|
|
13
|
+
* @param {any} Component
|
|
14
|
+
* @returns {boolean}
|
|
15
15
|
*/
|
|
16
|
-
check: () =>
|
|
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
|
+
};
|
package/nodes/editable-array.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@cloudcannon/
|
|
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
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
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[] |
|
|
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[] |
|
|
172
|
+
let value: unknown[] | CloudCannonVisualEditorAPIV1File[];
|
|
173
173
|
const __base_context = {
|
|
174
174
|
...this.contextBase,
|
|
175
175
|
};
|
package/nodes/editable-source.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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?:
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@cloudcannon/
|
|
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?:
|
|
26
|
-
collection?:
|
|
27
|
-
dataset?:
|
|
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
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
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<
|
|
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:
|
|
738
|
-
let file:
|
|
739
|
-
let dataset:
|
|
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.
|
|
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.
|
|
59
|
-
"@cloudcannon/
|
|
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.
|
|
63
|
-
"@types/react": "19.2.
|
|
70
|
+
"@types/node": "25.9.4",
|
|
71
|
+
"@types/react": "19.2.17",
|
|
64
72
|
"@types/react-dom": "19.2.3",
|
|
65
|
-
"astro": "6.
|
|
73
|
+
"astro": "6.4.8",
|
|
66
74
|
"js-beautify": "1.15.4",
|
|
67
|
-
"liquidjs": "10.
|
|
68
|
-
"typescript": "6.0.
|
|
75
|
+
"liquidjs": "10.27.0",
|
|
76
|
+
"typescript": "6.0.3"
|
|
69
77
|
},
|
|
70
78
|
"dependencies": {
|
|
71
|
-
"esbuild": "0.28.
|
|
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
|
+
}
|
package/types/cloudcannon.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export {};
|
|
|
3
3
|
declare global {
|
|
4
4
|
var ENV_CLIENT: boolean;
|
|
5
5
|
var inEditorMode: boolean;
|
|
6
|
-
var CloudCannonAPI:
|
|
6
|
+
var CloudCannonAPI: CloudCannonVisualEditorAPIRouter | undefined;
|
|
7
7
|
var CloudCannon:
|
|
8
|
-
|
|
|
9
|
-
|
|
|
8
|
+
| CloudCannonVisualEditorAPIV0
|
|
9
|
+
| CloudCannonVisualEditorAPIV1
|
|
10
10
|
| undefined;
|
|
11
11
|
}
|
package/types/modules.d.ts
CHANGED
|
@@ -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
|
+
}
|