@cloudcannon/editable-regions 0.0.15 → 0.0.17
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/integrations/astro/react-renderer.mjs +18 -3
- package/integrations/astro/svelte-renderer.mjs +65 -0
- package/integrations/svelte.mjs +40 -0
- package/nodes/editable-component.ts +6 -6
- package/nodes/editable-source.ts +6 -0
- package/package.json +16 -8
- package/types/astro.d.ts +4 -0
- package/types/modules.d.ts +8 -0
- package/types/svelte.d.ts +3 -0
|
@@ -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
|
+
};
|
|
@@ -215,6 +215,12 @@ export default class EditableComponent extends Editable {
|
|
|
215
215
|
continue;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// Both children are the same subclass (node/element) but are of different types (e.g. text -> comment)
|
|
219
|
+
if (renderChild.nodeName !== targetChild.nodeName) {
|
|
220
|
+
targetChild.replaceWith(renderChild);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
218
224
|
// Both existing and rendered children are nodes (i.e. some text)
|
|
219
225
|
if (
|
|
220
226
|
!(renderChild instanceof Element) &&
|
|
@@ -225,12 +231,6 @@ export default class EditableComponent extends Editable {
|
|
|
225
231
|
continue;
|
|
226
232
|
}
|
|
227
233
|
|
|
228
|
-
// Both children are elements but are different types
|
|
229
|
-
if (renderChild.nodeName !== targetChild.nodeName) {
|
|
230
|
-
targetChild.replaceWith(renderChild);
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
234
|
// Both existing and rendered children are the same kind of element, and neither is editable
|
|
235
235
|
if (!isEditableElement(renderChild) && !isEditableElement(targetChild)) {
|
|
236
236
|
// Update the existing element to match the rendered element and recurse their subtrees
|
package/nodes/editable-source.ts
CHANGED
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcannon/editable-regions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
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/visual-editor-api": "0.0.
|
|
66
|
+
"@biomejs/biome": "2.5.1",
|
|
67
|
+
"@cloudcannon/visual-editor-api": "0.0.19",
|
|
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.
|
|
75
|
+
"liquidjs": "10.27.1",
|
|
68
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/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
|
+
}
|