@cloudcannon/editable-regions 0.0.2

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.
Files changed (54) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +5 -0
  3. package/components/editable-array-component.ts +26 -0
  4. package/components/editable-array-item-component.ts +26 -0
  5. package/components/editable-component-component.ts +26 -0
  6. package/components/editable-image-component.ts +26 -0
  7. package/components/editable-snippet-component.ts +30 -0
  8. package/components/editable-source-component.ts +26 -0
  9. package/components/editable-text-component.ts +26 -0
  10. package/components/index.ts +35 -0
  11. package/components/ui/editable-array-item-controls.ts +123 -0
  12. package/components/ui/editable-component-controls.ts +57 -0
  13. package/components/ui/editable-region-error-card.ts +77 -0
  14. package/helpers/checks.ts +126 -0
  15. package/helpers/cloudcannon.ts +67 -0
  16. package/helpers/hydrate-editable-regions.ts +71 -0
  17. package/integrations/astro/astro-integration.mjs +110 -0
  18. package/integrations/astro/index.mjs +193 -0
  19. package/integrations/astro/modules/actions.js +74 -0
  20. package/integrations/astro/modules/assets.js +38 -0
  21. package/integrations/astro/modules/client-router.astro +5 -0
  22. package/integrations/astro/modules/content.js +116 -0
  23. package/integrations/astro/modules/i18n.js +76 -0
  24. package/integrations/astro/modules/image.astro +33 -0
  25. package/integrations/astro/modules/middleware.js +27 -0
  26. package/integrations/astro/modules/picture.astro +7 -0
  27. package/integrations/astro/modules/transitions.js +63 -0
  28. package/integrations/astro/react-renderer.mjs +40 -0
  29. package/integrations/react.mjs +32 -0
  30. package/nodes/editable-array-item.ts +427 -0
  31. package/nodes/editable-array.ts +241 -0
  32. package/nodes/editable-component.ts +273 -0
  33. package/nodes/editable-image.ts +253 -0
  34. package/nodes/editable-snippet.ts +148 -0
  35. package/nodes/editable-source.ts +245 -0
  36. package/nodes/editable-text.ts +163 -0
  37. package/nodes/editable.ts +471 -0
  38. package/nodes/index.ts +8 -0
  39. package/package.json +64 -0
  40. package/styles/editable-array-item.css +8 -0
  41. package/styles/editable-component.css +8 -0
  42. package/styles/editable-image.css +4 -0
  43. package/styles/editable-snippet.css +12 -0
  44. package/styles/editable-source.css +3 -0
  45. package/styles/editable-text.css +3 -0
  46. package/styles/index.css +101 -0
  47. package/styles/index.ts +6 -0
  48. package/styles/ui/editable-component-controls.css +104 -0
  49. package/styles/ui/editable-region-error-card.css +25 -0
  50. package/types/astro.d.ts +19 -0
  51. package/types/cloudcannon.d.ts +11 -0
  52. package/types/modules.d.ts +12 -0
  53. package/types/react.d.ts +5 -0
  54. package/types/vite.d.ts +9 -0
package/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright 2025 CloudCannon
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Editable Regions
2
+
3
+ Visual Editing for the [CloudCannon](https://cloudcannon.com/) CMS.
4
+
5
+ [<img src="https://img.shields.io/npm/v/@cloudcannon%2Feditable-regions?logo=npm" alt="version badge">](https://www.npmjs.com/package/@cloudcannon%2Feditable-regions)
@@ -0,0 +1,26 @@
1
+ import EditableArray from "../nodes/editable-array.js";
2
+
3
+ export default class EditableArrayComponent extends HTMLElement {
4
+ editable: EditableArray;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableArray(this);
9
+ }
10
+
11
+ connectedCallback(): void {
12
+ this.editable.connect();
13
+ }
14
+
15
+ disconnectedCallback(): void {
16
+ this.editable.disconnect();
17
+ }
18
+ }
19
+
20
+ customElements.define("editable-array", EditableArrayComponent);
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "editable-array": EditableArrayComponent;
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import EditableArrayItem from "../nodes/editable-array-item.js";
2
+
3
+ export default class EditableArrayItemComponent extends HTMLElement {
4
+ editable: EditableArrayItem;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableArrayItem(this);
9
+ }
10
+
11
+ connectedCallback(): void {
12
+ this.editable.connect();
13
+ }
14
+
15
+ disconnectedCallback(): void {
16
+ this.editable.disconnect();
17
+ }
18
+ }
19
+
20
+ customElements.define("editable-array-item", EditableArrayItemComponent);
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "editable-array-item": EditableArrayItemComponent;
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import EditableComponent from "../nodes/editable-component.js";
2
+
3
+ export default class EditableComponentComponent extends HTMLElement {
4
+ editable: EditableComponent;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableComponent(this);
9
+ }
10
+
11
+ connectedCallback(): void {
12
+ this.editable.connect();
13
+ }
14
+
15
+ disconnectedCallback(): void {
16
+ this.editable.disconnect();
17
+ }
18
+ }
19
+
20
+ customElements.define("editable-component", EditableComponentComponent);
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "editable-component": EditableComponentComponent;
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import EditableImage from "../nodes/editable-image.js";
2
+
3
+ export default class EditableImageComponent extends HTMLElement {
4
+ editable: EditableImage;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableImage(this);
9
+ }
10
+
11
+ connectedCallback(): void {
12
+ this.editable.connect();
13
+ }
14
+
15
+ disconnectedCallback(): void {
16
+ this.editable.disconnect();
17
+ }
18
+ }
19
+
20
+ customElements.define("editable-image", EditableImageComponent);
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "editable-image": EditableImageComponent;
25
+ }
26
+ }
@@ -0,0 +1,30 @@
1
+ import EditableSnippet from "../nodes/editable-snippet.js";
2
+
3
+ export default class EditableSnippetComponent extends HTMLElement {
4
+ editable: EditableSnippet;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableSnippet(this);
9
+ }
10
+
11
+ set snippetData(value: unknown) {
12
+ this.editable.pushValue(value);
13
+ }
14
+
15
+ connectedCallback(): void {
16
+ this.editable.connect();
17
+ }
18
+
19
+ disconnectedCallback(): void {
20
+ this.editable.disconnect();
21
+ }
22
+ }
23
+
24
+ customElements.define("editable-snippet", EditableSnippetComponent);
25
+
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ "editable-snippet": EditableSnippetComponent;
29
+ }
30
+ }
@@ -0,0 +1,26 @@
1
+ import EditableSource from "../nodes/editable-source.js";
2
+
3
+ export default class EditableComponentSource extends HTMLElement {
4
+ editable: EditableSource;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableSource(this);
9
+ }
10
+
11
+ connectedCallback(): void {
12
+ this.editable.connect();
13
+ }
14
+
15
+ disconnectedCallback(): void {
16
+ this.editable.disconnect();
17
+ }
18
+ }
19
+
20
+ customElements.define("editable-source", EditableComponentSource);
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "editable-source": EditableComponentSource;
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import EditableText from "../nodes/editable-text.js";
2
+
3
+ export default class EditableTextComponent extends HTMLElement {
4
+ editable: EditableText;
5
+
6
+ constructor() {
7
+ super();
8
+ this.editable = new EditableText(this);
9
+ }
10
+
11
+ connectedCallback(): void {
12
+ this.editable.connect();
13
+ }
14
+
15
+ disconnectedCallback(): void {
16
+ this.editable.disconnect();
17
+ }
18
+ }
19
+
20
+ customElements.define("editable-text", EditableTextComponent);
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "editable-text": EditableTextComponent;
25
+ }
26
+ }
@@ -0,0 +1,35 @@
1
+ export { default as EditableArrayComponent } from "./editable-array-component.js";
2
+ export { default as EditableArrayItemComponent } from "./editable-array-item-component.js";
3
+ export { default as EditableTextComponent } from "./editable-text-component.js";
4
+ export { default as EditableComponentComponent } from "./editable-component-component.js";
5
+ export { default as EditableImageComponent } from "./editable-image-component.js";
6
+ export { default as EditableSourceComponent } from "./editable-source-component.js";
7
+ export { default as EditableSnippetComponent } from "./editable-snippet-component.js";
8
+
9
+ import { loadedPromise } from "../helpers/cloudcannon.js";
10
+ import {
11
+ dehydrateDataEditableRegions,
12
+ hydrateDataEditableRegions,
13
+ } from "../helpers/hydrate-editable-regions";
14
+
15
+ const observer = new MutationObserver((mutations) => {
16
+ mutations.forEach((mutation) => {
17
+ mutation.removedNodes.forEach((el) => {
18
+ if (el instanceof HTMLElement) {
19
+ dehydrateDataEditableRegions(el);
20
+ }
21
+ });
22
+
23
+ mutation.addedNodes.forEach((el) => {
24
+ if (el instanceof HTMLElement) {
25
+ hydrateDataEditableRegions(el);
26
+ }
27
+ });
28
+ });
29
+ });
30
+
31
+ observer.observe(document, { childList: true, subtree: true });
32
+
33
+ loadedPromise.then(() => {
34
+ hydrateDataEditableRegions(document.body);
35
+ });
@@ -0,0 +1,123 @@
1
+ import type { ArrayDirection } from "../../nodes/editable-array";
2
+ import EditableComponentControls from "./editable-component-controls";
3
+
4
+ export default class EditableArrayItemControls extends EditableComponentControls {
5
+ arrayDirection: ArrayDirection = "column";
6
+
7
+ moveBackwardText: "up" | "left" = "up";
8
+ moveForwardText: "down" | "right" = "down";
9
+
10
+ private _disableMoveForward = false;
11
+ private _disableMoveBackward = false;
12
+ private _disableRemove = false;
13
+ private _disableReorder = false;
14
+
15
+ private moveForwardButton?: HTMLButtonElement;
16
+ private moveBackwardButton?: HTMLButtonElement;
17
+ private deleteButton?: HTMLButtonElement;
18
+ private dragHandle?: HTMLButtonElement;
19
+
20
+ set disableMoveForward(value: boolean) {
21
+ this._disableMoveForward = value;
22
+ this.update();
23
+ }
24
+
25
+ set disableMoveBackward(value: boolean) {
26
+ this._disableMoveBackward = value;
27
+ this.update();
28
+ }
29
+
30
+ set disableRemove(value: boolean) {
31
+ this._disableRemove = value;
32
+ this.update();
33
+ }
34
+
35
+ set disableReorder(value: boolean) {
36
+ this._disableReorder = value;
37
+ this.update();
38
+ }
39
+
40
+ update() {
41
+ if (this.dragHandle) {
42
+ this.dragHandle.draggable = !this._disableReorder;
43
+ this.dragHandle.innerHTML = `<cc-icon name="${this.dragHandle.draggable ? "drag_indicator" : "more_vert"}"></cc-icon>`;
44
+ }
45
+
46
+ if (this.moveForwardButton) {
47
+ this.moveForwardButton.disabled =
48
+ this._disableMoveForward || this._disableReorder;
49
+ }
50
+
51
+ if (this.moveBackwardButton) {
52
+ this.moveBackwardButton.disabled =
53
+ this._disableMoveBackward || this._disableReorder;
54
+ }
55
+
56
+ if (this.deleteButton) {
57
+ this.deleteButton.disabled = this._disableRemove;
58
+ }
59
+ }
60
+
61
+ render(shadow: ShadowRoot): void {
62
+ super.render(shadow);
63
+
64
+ this.dragHandle = document.createElement("button");
65
+ this.dragHandle.onclick = (e) => {
66
+ e.stopPropagation();
67
+ if (this.contextMenu?.classList.contains("open")) {
68
+ this.contextMenu?.classList.remove("open");
69
+ this.removeAttribute("open");
70
+ } else if (this.contextMenu && this.contextMenu.childElementCount > 0) {
71
+ this.contextMenu?.classList.add("open");
72
+ this.setAttribute("open", "true");
73
+ }
74
+ };
75
+ this.dragHandle.ondragstart = () => {
76
+ this.contextMenu?.classList.remove("open");
77
+ this.removeAttribute("open");
78
+ };
79
+ this.buttonRow?.append(this.dragHandle);
80
+
81
+ const backwardIconName = this.moveBackwardText === "up" ? "north" : "west";
82
+ this.moveBackwardButton = document.createElement("button");
83
+ this.moveBackwardButton.innerHTML = `<cc-icon name="${backwardIconName}"></cc-icon> Move ${this.moveBackwardText}`;
84
+ this.moveBackwardButton.onclick = () => {
85
+ this.dispatchEvent(new CustomEvent("move-backward", { detail: this }));
86
+ };
87
+ const moveBackwardContainer = document.createElement("li");
88
+ moveBackwardContainer.append(this.moveBackwardButton);
89
+ this.contextMenu?.append(moveBackwardContainer);
90
+
91
+ const forwardIconName = this.moveForwardText === "down" ? "south" : "east";
92
+ this.moveForwardButton = document.createElement("button");
93
+ this.moveForwardButton.innerHTML = `<cc-icon name="${forwardIconName}"></cc-icon> Move ${this.moveForwardText}`;
94
+ this.moveForwardButton.onclick = (): void => {
95
+ this.dispatchEvent(new CustomEvent("move-forward", { detail: this }));
96
+ };
97
+ const moveForwardContainer = document.createElement("li");
98
+ moveForwardContainer.append(this.moveForwardButton);
99
+ this.contextMenu?.append(moveForwardContainer);
100
+
101
+ this.deleteButton = document.createElement("button");
102
+ this.deleteButton.innerHTML = '<cc-icon name="delete"></cc-icon> Delete';
103
+ this.deleteButton.onclick = () => {
104
+ this.dispatchEvent(new CustomEvent("delete", { detail: this }));
105
+ };
106
+ const deleteContainer = document.createElement("li");
107
+ deleteContainer.append(this.deleteButton);
108
+ this.contextMenu?.append(deleteContainer);
109
+
110
+ this.update();
111
+ }
112
+ }
113
+
114
+ customElements.define(
115
+ "editable-array-item-controls",
116
+ EditableArrayItemControls,
117
+ );
118
+
119
+ declare global {
120
+ interface HTMLElementTagNameMap {
121
+ "editable-array-item-controls": EditableArrayItemControls;
122
+ }
123
+ }
@@ -0,0 +1,57 @@
1
+ import styleContent from "../../styles/ui/editable-component-controls.css?inline";
2
+
3
+ export default class EditableComponentControls extends HTMLElement {
4
+ protected shadow?: ShadowRoot;
5
+ protected contextMenu?: HTMLUListElement;
6
+ protected buttonRow?: HTMLDivElement;
7
+
8
+ private editButton?: HTMLButtonElement;
9
+
10
+ render(shadow: ShadowRoot): void {
11
+ const style = document.createElement("style");
12
+ style.textContent = styleContent;
13
+ shadow.appendChild(style);
14
+
15
+ this.buttonRow = document.createElement("div");
16
+ this.buttonRow.classList.add("button-row");
17
+ shadow.appendChild(this.buttonRow);
18
+
19
+ this.contextMenu = document.createElement("ul");
20
+ this.contextMenu.classList.add("context-menu");
21
+ shadow.appendChild(this.contextMenu);
22
+
23
+ this.editButton = document.createElement("button");
24
+ this.editButton.innerHTML = '<cc-icon name="edit"></cc-icon>';
25
+ this.editButton.onclick = () => {
26
+ this.dispatchEvent(new CustomEvent("edit", { detail: this }));
27
+ };
28
+ this.buttonRow.append(this.editButton);
29
+
30
+ this.onclick = () => {
31
+ this.contextMenu?.classList.remove("open");
32
+ this.removeAttribute("open");
33
+ };
34
+
35
+ this.onblur = () => {
36
+ this.contextMenu?.classList.remove("open");
37
+ this.removeAttribute("open");
38
+ };
39
+ }
40
+
41
+ connectedCallback() {
42
+ if (this.shadow) {
43
+ return;
44
+ }
45
+
46
+ this.shadow = this.attachShadow({ mode: "open" });
47
+ this.render(this.shadow);
48
+ }
49
+ }
50
+
51
+ customElements.define("editable-component-controls", EditableComponentControls);
52
+
53
+ declare global {
54
+ interface HTMLElementTagNameMap {
55
+ "editable-component-controls": EditableComponentControls;
56
+ }
57
+ }
@@ -0,0 +1,77 @@
1
+ import styleContent from "../../styles/ui/editable-region-error-card.css?inline";
2
+
3
+ export default class EditableRegionErrorCard extends HTMLElement {
4
+ private _error?: unknown;
5
+ private shadow?: ShadowRoot;
6
+
7
+ set error(err: unknown) {
8
+ this._error = err;
9
+ if (this.shadow) {
10
+ this.render(this.shadow, this._error);
11
+ }
12
+ }
13
+
14
+ connectedCallback() {
15
+ if (this.shadow) {
16
+ return;
17
+ }
18
+
19
+ this.shadow = this.attachShadow({ mode: "open" });
20
+
21
+ this.render(this.shadow, this._error);
22
+ }
23
+
24
+ render(shadow: ShadowRoot, error: unknown) {
25
+ const style = document.createElement("style");
26
+ style.textContent = styleContent;
27
+ shadow.appendChild(style);
28
+
29
+ const container = document.createElement("div");
30
+ container.className = "container";
31
+ shadow.appendChild(container);
32
+
33
+ const icon = document.createElement("cc-icon");
34
+ icon.setAttribute("name", "warning");
35
+ container.appendChild(icon);
36
+
37
+ const body = document.createElement("div");
38
+ container.appendChild(body);
39
+
40
+ const heading = document.createElement("p");
41
+ heading.className = "heading";
42
+ heading.innerHTML = this.getAttribute("heading") ?? "Error";
43
+
44
+ body.appendChild(heading);
45
+
46
+ if (this.hasAttribute("message")) {
47
+ const message = document.createElement("p");
48
+ message.innerHTML = this.getAttribute("message") ?? "";
49
+
50
+ body.appendChild(message);
51
+ } else if (error instanceof Error) {
52
+ const message = document.createElement("p");
53
+ message.innerHTML = error.message;
54
+
55
+ body.appendChild(message);
56
+
57
+ if (error.stack) {
58
+ const stack = document.createElement("pre");
59
+ stack.innerHTML = error.stack
60
+ .replace(new RegExp(window.location.origin, "g"), "")
61
+ .split("\n")
62
+ .slice(0, 5)
63
+ .join("\n");
64
+
65
+ body.appendChild(stack);
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ customElements.define("editable-region-error-card", EditableRegionErrorCard);
72
+
73
+ declare global {
74
+ interface HTMLElementTagNameMap {
75
+ "editable-region-error-card": EditableRegionErrorCard;
76
+ }
77
+ }
@@ -0,0 +1,126 @@
1
+ import EditableArrayItem from "../nodes/editable-array-item.js";
2
+ import EditableText from "../nodes/editable-text.js";
3
+ import Editable from "../nodes/editable.js";
4
+
5
+ const TAG_NAMES = [
6
+ "EDITABLE-TEXT",
7
+ "EDITABLE-COMPONENT",
8
+ "EDITABLE-ARRAY-ITEM",
9
+ "EDITABLE-ARRAY",
10
+ "EDITABLE-IMAGE",
11
+ "EDITABLE-SOURCE",
12
+ "EDITABLE-SNIPPET",
13
+ ];
14
+
15
+ const EDITABLE_REGION_TYPES = [
16
+ "text",
17
+ "component",
18
+ "array",
19
+ "array-item",
20
+ "image",
21
+ "source",
22
+ ];
23
+
24
+ const CORRESPONDING_NAME: Record<string, string> = {
25
+ "EDITABLE-TEXT": "text",
26
+ "EDITABLE-COMPONENT": "component",
27
+ "EDITABLE-ARRAY-ITEM": "array-item",
28
+ "EDITABLE-ARRAY": "array",
29
+ "EDITABLE-IMAGE": "image",
30
+ "EDITABLE-SOURCE": "source",
31
+ };
32
+
33
+ export const hasEditable = <T extends object>(
34
+ el: T,
35
+ ): el is T & { editable: Editable } => {
36
+ return "editable" in el && el.editable instanceof Editable;
37
+ };
38
+
39
+ export const hasEditableText = <T extends object>(
40
+ el: T,
41
+ ): el is T & { editable: EditableText } => {
42
+ return "editable" in el && el.editable instanceof EditableText;
43
+ };
44
+
45
+ export const hasEditableArrayItem = <T extends object>(
46
+ el: T,
47
+ ): el is T & { editable: EditableArrayItem } => {
48
+ return "editable" in el && el.editable instanceof EditableArrayItem;
49
+ };
50
+
51
+ export const isEditableElement = (el: unknown): boolean => {
52
+ if (!(el instanceof HTMLElement)) {
53
+ return false;
54
+ }
55
+
56
+ return (
57
+ TAG_NAMES.includes(el.tagName) ||
58
+ (!!el.dataset.editable &&
59
+ EDITABLE_REGION_TYPES.includes(el.dataset.editable))
60
+ );
61
+ };
62
+
63
+ export const areEqualEditables = (a: HTMLElement, b: HTMLElement) => {
64
+ if (
65
+ a.tagName !== b.tagName &&
66
+ a.dataset.editable !== b.dataset.editable &&
67
+ CORRESPONDING_NAME[a.tagName] !== b.dataset.editable &&
68
+ CORRESPONDING_NAME[b.tagName] !== a.dataset.editable
69
+ ) {
70
+ return false;
71
+ }
72
+
73
+ if (Object.keys(a.dataset).length !== Object.keys(b.dataset).length) {
74
+ return false;
75
+ }
76
+
77
+ return Object.keys(a.dataset).every(
78
+ (key) => a.dataset[key] === b.dataset[key],
79
+ );
80
+ };
81
+
82
+ export const isEditableText = (el?: Element | null): boolean => {
83
+ return (
84
+ el?.tagName === "EDITABLE-TEXT" ||
85
+ (el instanceof HTMLElement && el.dataset.editable === "text")
86
+ );
87
+ };
88
+
89
+ export const isEditableArrayItem = (el?: Element | null): boolean => {
90
+ return (
91
+ el?.tagName === "EDITABLE-ARRAY-ITEM" ||
92
+ (el instanceof HTMLElement && el.dataset.editable === "array-item")
93
+ );
94
+ };
95
+
96
+ export const areEqualNodes = (a: ChildNode, b: ChildNode) => {
97
+ if (a.nodeName !== b.nodeName) {
98
+ return false;
99
+ }
100
+
101
+ if (a instanceof Element && b instanceof Element) {
102
+ if (a.className !== b.className) {
103
+ return false;
104
+ }
105
+
106
+ if (a.id !== b.id) {
107
+ return false;
108
+ }
109
+
110
+ if (a.attributes.length !== b.attributes.length) {
111
+ return false;
112
+ }
113
+
114
+ for (let i = 0; i < a.attributes.length; i++) {
115
+ if (
116
+ a.attributes[i].name !== b.attributes[i].name ||
117
+ a.attributes[i].value !== b.attributes[i].value
118
+ ) {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ return true;
124
+ }
125
+ return a.isEqualNode(b);
126
+ };
@@ -0,0 +1,67 @@
1
+ import type {
2
+ CloudCannonEditorWindow,
3
+ CloudCannonJavaScriptV1API,
4
+ } from "@cloudcannon/javascript-api";
5
+
6
+ export type ComponentRenderer = (
7
+ props: any,
8
+ ) => HTMLElement | Promise<HTMLElement>;
9
+
10
+ declare const window: CloudCannonEditorWindow & {
11
+ cc_components?: Record<string, ComponentRenderer>;
12
+ cc_snippets?: Record<string, ComponentRenderer>;
13
+ };
14
+
15
+ let _cloudcannon: CloudCannonJavaScriptV1API;
16
+
17
+ const apiLoadedPromise = new Promise<void>((resolve) => {
18
+ if (window.CloudCannonAPI) {
19
+ _cloudcannon = window.CloudCannonAPI.useVersion("v1") as any;
20
+ resolve();
21
+ } else {
22
+ document.addEventListener(
23
+ "cloudcannon:load",
24
+ () => {
25
+ if (window.CloudCannonAPI) {
26
+ _cloudcannon = window.CloudCannonAPI.useVersion("v1") as any;
27
+ }
28
+ return resolve();
29
+ },
30
+ { once: true },
31
+ );
32
+ }
33
+ });
34
+
35
+ export const loadedPromise = Promise.all([
36
+ apiLoadedPromise,
37
+ customElements.whenDefined("editable-array-item"),
38
+ customElements.whenDefined("editable-array"),
39
+ customElements.whenDefined("editable-text"),
40
+ customElements.whenDefined("editable-component"),
41
+ customElements.whenDefined("editable-image"),
42
+ customElements.whenDefined("editable-source"),
43
+ customElements.whenDefined("editable-snippet"),
44
+ ]);
45
+
46
+ export const addEditableComponentRenderer = (
47
+ key: string,
48
+ renderer: ComponentRenderer,
49
+ ) => {
50
+ window.cc_components = window.cc_components || {};
51
+ window.cc_components[key] = renderer;
52
+ document.dispatchEvent(new CustomEvent(`editable-regions:registered-${key}`));
53
+ };
54
+
55
+ export const addEditableSnippetRenderer = (
56
+ key: string,
57
+ renderer: ComponentRenderer,
58
+ ) => {
59
+ window.cc_snippets = window.cc_snippets || {};
60
+ window.cc_snippets[key] = renderer;
61
+ document.dispatchEvent(new CustomEvent(`editable-regions:registered-${key}`));
62
+ };
63
+
64
+ export const getEditableComponentRenderers = () => window.cc_components ?? {};
65
+ export const getEditableSnippetRenderers = () => window.cc_snippets ?? {};
66
+
67
+ export { _cloudcannon as CloudCannon };