@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.
- package/LICENSE +5 -0
- package/README.md +5 -0
- package/components/editable-array-component.ts +26 -0
- package/components/editable-array-item-component.ts +26 -0
- package/components/editable-component-component.ts +26 -0
- package/components/editable-image-component.ts +26 -0
- package/components/editable-snippet-component.ts +30 -0
- package/components/editable-source-component.ts +26 -0
- package/components/editable-text-component.ts +26 -0
- package/components/index.ts +35 -0
- package/components/ui/editable-array-item-controls.ts +123 -0
- package/components/ui/editable-component-controls.ts +57 -0
- package/components/ui/editable-region-error-card.ts +77 -0
- package/helpers/checks.ts +126 -0
- package/helpers/cloudcannon.ts +67 -0
- package/helpers/hydrate-editable-regions.ts +71 -0
- package/integrations/astro/astro-integration.mjs +110 -0
- package/integrations/astro/index.mjs +193 -0
- package/integrations/astro/modules/actions.js +74 -0
- package/integrations/astro/modules/assets.js +38 -0
- package/integrations/astro/modules/client-router.astro +5 -0
- package/integrations/astro/modules/content.js +116 -0
- package/integrations/astro/modules/i18n.js +76 -0
- package/integrations/astro/modules/image.astro +33 -0
- package/integrations/astro/modules/middleware.js +27 -0
- package/integrations/astro/modules/picture.astro +7 -0
- package/integrations/astro/modules/transitions.js +63 -0
- package/integrations/astro/react-renderer.mjs +40 -0
- package/integrations/react.mjs +32 -0
- package/nodes/editable-array-item.ts +427 -0
- package/nodes/editable-array.ts +241 -0
- package/nodes/editable-component.ts +273 -0
- package/nodes/editable-image.ts +253 -0
- package/nodes/editable-snippet.ts +148 -0
- package/nodes/editable-source.ts +245 -0
- package/nodes/editable-text.ts +163 -0
- package/nodes/editable.ts +471 -0
- package/nodes/index.ts +8 -0
- package/package.json +64 -0
- package/styles/editable-array-item.css +8 -0
- package/styles/editable-component.css +8 -0
- package/styles/editable-image.css +4 -0
- package/styles/editable-snippet.css +12 -0
- package/styles/editable-source.css +3 -0
- package/styles/editable-text.css +3 -0
- package/styles/index.css +101 -0
- package/styles/index.ts +6 -0
- package/styles/ui/editable-component-controls.css +104 -0
- package/styles/ui/editable-region-error-card.css +25 -0
- package/types/astro.d.ts +19 -0
- package/types/cloudcannon.d.ts +11 -0
- package/types/modules.d.ts +12 -0
- package/types/react.d.ts +5 -0
- package/types/vite.d.ts +9 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
const props = Astro.props;
|
|
3
|
+
|
|
4
|
+
if (props.alt === undefined || props.alt === null) {
|
|
5
|
+
throw new Error("Image missing 'alt'");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// As a convenience, allow width and height to be string with a number in them, to match HTML's native `img`.
|
|
9
|
+
if (typeof props.width === "string") {
|
|
10
|
+
props.width = Number.parseInt(props.width);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof props.height === "string") {
|
|
14
|
+
props.height = Number.parseInt(props.height);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!props || typeof props !== "object") {
|
|
18
|
+
throw new Error("Image missing options");
|
|
19
|
+
}
|
|
20
|
+
if (typeof props.src === "undefined") {
|
|
21
|
+
throw new Error("Image missing 'src'");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const resolvedSrc =
|
|
25
|
+
typeof props.src === "object" && "then" in props.src
|
|
26
|
+
? ((await props.src).default ?? (await props.src))
|
|
27
|
+
: props.src;
|
|
28
|
+
|
|
29
|
+
const src = typeof resolvedSrc === "object" ? resolvedSrc.src : resolvedSrc;
|
|
30
|
+
delete props.src;
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<img src={src} {...props} />
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const sequence = () => {
|
|
2
|
+
console.warn(
|
|
3
|
+
"[CloudCannon] middleware is not supported in an editable component. Please use an editing fallback instead.",
|
|
4
|
+
);
|
|
5
|
+
return () => {};
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const defineMiddleware = () => {
|
|
9
|
+
console.warn(
|
|
10
|
+
"[CloudCannon] middleware is not supported in an editable component. Please use an editing fallback instead.",
|
|
11
|
+
);
|
|
12
|
+
return () => {};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const createContext = () => {
|
|
16
|
+
console.warn(
|
|
17
|
+
"[CloudCannon] middleware is not supported in an editable component. Please use an editing fallback instead.",
|
|
18
|
+
);
|
|
19
|
+
return {};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const trySerializeLocals = () => {
|
|
23
|
+
console.warn(
|
|
24
|
+
"[CloudCannon] middleware is not supported in an editable component. Please use an editing fallback instead.",
|
|
25
|
+
);
|
|
26
|
+
return "";
|
|
27
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import ClientRouterInternal from "./client-router.astro";
|
|
2
|
+
|
|
3
|
+
export const ClientRouter = ClientRouterInternal;
|
|
4
|
+
|
|
5
|
+
export const fade = () => {
|
|
6
|
+
console.warn(
|
|
7
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
8
|
+
);
|
|
9
|
+
return {};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const slide = () => {
|
|
13
|
+
console.warn(
|
|
14
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
15
|
+
);
|
|
16
|
+
return {};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const navigate = () => {
|
|
20
|
+
console.warn(
|
|
21
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const supportsViewTransitions = false;
|
|
26
|
+
|
|
27
|
+
export const transitionEnabledOnThisPage = false;
|
|
28
|
+
|
|
29
|
+
export const getFallback = () => {
|
|
30
|
+
console.warn(
|
|
31
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
32
|
+
);
|
|
33
|
+
return "none";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const swapFunctions = {
|
|
37
|
+
deselectScripts: () => {
|
|
38
|
+
console.warn(
|
|
39
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
swapRootAttributes: () => {
|
|
43
|
+
console.warn(
|
|
44
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
45
|
+
);
|
|
46
|
+
},
|
|
47
|
+
swapHeadElements: () => {
|
|
48
|
+
console.warn(
|
|
49
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
saveFocus: () => {
|
|
53
|
+
console.warn(
|
|
54
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
55
|
+
);
|
|
56
|
+
return () => {};
|
|
57
|
+
},
|
|
58
|
+
swapBodyElement: () => {
|
|
59
|
+
console.warn(
|
|
60
|
+
"[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server.browser";
|
|
5
|
+
|
|
6
|
+
import { addFrameworkRenderer, queueForClientSideRender } from "./index.mjs";
|
|
7
|
+
|
|
8
|
+
addFrameworkRenderer({
|
|
9
|
+
name: "@astrojs/react",
|
|
10
|
+
clientEntrypoint: "@astrojs/react/client.js",
|
|
11
|
+
ssr: {
|
|
12
|
+
/**
|
|
13
|
+
* Always returns true to handle all remaining components as React.
|
|
14
|
+
* @returns {boolean} Always true
|
|
15
|
+
*/
|
|
16
|
+
check: () => true,
|
|
17
|
+
/**
|
|
18
|
+
* Renders a React component to static markup or queues for client-side rendering.
|
|
19
|
+
* @param {any} Component - The React component function
|
|
20
|
+
* @param {any} props - Props to pass to the component
|
|
21
|
+
* @returns {Promise<{ html: string }>} Object containing the rendered HTML
|
|
22
|
+
*/
|
|
23
|
+
renderToStaticMarkup: async (Component, props) => {
|
|
24
|
+
try {
|
|
25
|
+
const reactNode = Component(props);
|
|
26
|
+
return { html: renderToStaticMarkup(reactNode) };
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const id = queueForClientSideRender((node) => {
|
|
29
|
+
const reactNode = createElement(Component, props, null);
|
|
30
|
+
const root = createRoot(node);
|
|
31
|
+
flushSync(() => root.render(reactNode));
|
|
32
|
+
});
|
|
33
|
+
// Queue for client-side rendering if SSR fails
|
|
34
|
+
return {
|
|
35
|
+
html: `<div data-editable-region-csr-id=${id}></div>`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { addEditableComponentRenderer } from "../helpers/cloudcannon";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Registers a React component with the CloudCannon component system.
|
|
8
|
+
* Creates a wrapper that renders the React component to an HTMLElement.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} key - Unique identifier for the component
|
|
11
|
+
* @param {any} component - The React component function to register
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
export const registerReactComponent = (key, component) => {
|
|
15
|
+
/**
|
|
16
|
+
* Wrapper function that renders the React component to an HTMLElement.
|
|
17
|
+
*
|
|
18
|
+
* @param {any} props - Props to pass to the React component
|
|
19
|
+
* @returns {HTMLElement} The rendered component as an HTMLElement
|
|
20
|
+
*/
|
|
21
|
+
const wrappedComponent = (props) => {
|
|
22
|
+
const reactNode = createElement(component, props, null);
|
|
23
|
+
const rootEl = document.createElement("div");
|
|
24
|
+
const root = createRoot(rootEl);
|
|
25
|
+
|
|
26
|
+
flushSync(() => root.render(reactNode));
|
|
27
|
+
|
|
28
|
+
return rootEl;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
addEditableComponentRenderer(key, wrappedComponent);
|
|
32
|
+
};
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import "../components/ui/editable-array-item-controls.js";
|
|
2
|
+
import type EditableArrayItemControls from "../components/ui/editable-array-item-controls.js";
|
|
3
|
+
import {
|
|
4
|
+
hasEditableArrayItem,
|
|
5
|
+
isEditableArrayItem,
|
|
6
|
+
} from "../helpers/checks.js";
|
|
7
|
+
import { CloudCannon } from "../helpers/cloudcannon.js";
|
|
8
|
+
import EditableArray from "./editable-array.js";
|
|
9
|
+
import EditableComponent from "./editable-component.js";
|
|
10
|
+
|
|
11
|
+
export default class EditableArrayItem extends EditableComponent {
|
|
12
|
+
parent: EditableArray | null = null;
|
|
13
|
+
|
|
14
|
+
protected controlsElement?: EditableArrayItemControls;
|
|
15
|
+
|
|
16
|
+
private inputConfig?: any;
|
|
17
|
+
|
|
18
|
+
validateConfiguration(): boolean {
|
|
19
|
+
const key = this.element.dataset.component;
|
|
20
|
+
if (key) {
|
|
21
|
+
const component = this.getComponents()[key];
|
|
22
|
+
if (!component) {
|
|
23
|
+
this.element.classList.add("errored");
|
|
24
|
+
const error = document.createElement("editable-region-error-card");
|
|
25
|
+
error.setAttribute("heading", "Failed to render component");
|
|
26
|
+
error.setAttribute("message", `Couldn't find component '${key}'`);
|
|
27
|
+
this.element.replaceChildren(error);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!this.parent || !(this.parent instanceof EditableArray)) {
|
|
33
|
+
this.element.classList.add("errored");
|
|
34
|
+
const error = document.createElement("editable-region-error-card");
|
|
35
|
+
error.setAttribute("heading", "Failed to render array item");
|
|
36
|
+
error.setAttribute(
|
|
37
|
+
"message",
|
|
38
|
+
"Parent array editable not found. Array items must be a descendant of an array editable.",
|
|
39
|
+
);
|
|
40
|
+
this.element.replaceChildren(error);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onHover(e: DragEvent): void {
|
|
48
|
+
const source = this.parent?.resolveSource();
|
|
49
|
+
if (!source || !e.dataTransfer) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (
|
|
54
|
+
!e.dataTransfer?.types.includes(source) &&
|
|
55
|
+
!e.dataTransfer.types.includes(this.getDragType())
|
|
56
|
+
) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
this.element.classList.add("dragover");
|
|
62
|
+
this.element.style.boxShadow = this.getDraggingBoxShadow(e);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getDragType(): string {
|
|
66
|
+
if (this.inputConfig?.options?.structures?.values?.length) {
|
|
67
|
+
return "cc:structure";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const currentArraySubtype = this.inputConfig?.options?.__array_subtype;
|
|
71
|
+
if (currentArraySubtype) {
|
|
72
|
+
return `cc:${currentArraySubtype}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const type = CloudCannon.getInputType(this.resolveSource(), this.value);
|
|
76
|
+
return `cc:${type}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getDraggingBoxShadow(e: DragEvent): string {
|
|
80
|
+
const position = this.getDragPosition(e);
|
|
81
|
+
const arrayDirection = this.parent?.arrayDirection || "column";
|
|
82
|
+
|
|
83
|
+
const column = arrayDirection.startsWith("column");
|
|
84
|
+
const reversed = arrayDirection.endsWith("reverse");
|
|
85
|
+
|
|
86
|
+
if (column) {
|
|
87
|
+
if (reversed) {
|
|
88
|
+
if (position === "before") {
|
|
89
|
+
return "0 3px 0 var(--ccve-color-sol)";
|
|
90
|
+
}
|
|
91
|
+
return "0 -3px 0 var(--ccve-color-sol)";
|
|
92
|
+
}
|
|
93
|
+
if (position === "before") {
|
|
94
|
+
return "0 -3px 0 var(--ccve-color-sol)";
|
|
95
|
+
}
|
|
96
|
+
return "0 3px 0 var(--ccve-color-sol)";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (reversed) {
|
|
100
|
+
if (position === "before") {
|
|
101
|
+
return "3px 0 0 var(--ccve-color-sol)";
|
|
102
|
+
}
|
|
103
|
+
return "-3px 0 0 var(--ccve-color-sol)";
|
|
104
|
+
}
|
|
105
|
+
if (position === "before") {
|
|
106
|
+
return "-3px 0 0 var(--ccve-color-sol)";
|
|
107
|
+
}
|
|
108
|
+
return "3px 0 0 var(--ccve-color-sol)";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getDragPosition(e: DragEvent): "before" | "after" {
|
|
112
|
+
const rect = this.element.getBoundingClientRect();
|
|
113
|
+
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
114
|
+
|
|
115
|
+
const mousePos = arrayDirection.startsWith("row") ? e.clientX : e.clientY;
|
|
116
|
+
const elementPos = arrayDirection.startsWith("row") ? rect.left : rect.top;
|
|
117
|
+
const elementSize = arrayDirection.startsWith("row")
|
|
118
|
+
? rect.width
|
|
119
|
+
: rect.height;
|
|
120
|
+
|
|
121
|
+
const relativePos = mousePos - elementPos;
|
|
122
|
+
const isInFirstHalf = relativePos < elementSize / 2;
|
|
123
|
+
const isBefore = arrayDirection.endsWith("reverse")
|
|
124
|
+
? !isInFirstHalf
|
|
125
|
+
: isInFirstHalf;
|
|
126
|
+
|
|
127
|
+
return isBefore ? "before" : "after";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
dispatchArrayMove(fromIndex: number, toIndex: number) {
|
|
131
|
+
this.element.dispatchEvent(
|
|
132
|
+
new CustomEvent("cloudcannon-api", {
|
|
133
|
+
bubbles: true,
|
|
134
|
+
detail: {
|
|
135
|
+
action: "move-array-item",
|
|
136
|
+
fromIndex,
|
|
137
|
+
toIndex,
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
dispatchArrayRemove(fromIndex: number, source?: string) {
|
|
144
|
+
this.element.dispatchEvent(
|
|
145
|
+
new CustomEvent("cloudcannon-api", {
|
|
146
|
+
bubbles: true,
|
|
147
|
+
detail: {
|
|
148
|
+
action: "remove-array-item",
|
|
149
|
+
fromIndex,
|
|
150
|
+
source,
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
dispatchArrayAdd(newIndex: number, value: unknown) {
|
|
157
|
+
this.element.dispatchEvent(
|
|
158
|
+
new CustomEvent("cloudcannon-api", {
|
|
159
|
+
bubbles: true,
|
|
160
|
+
detail: {
|
|
161
|
+
action: "add-array-item",
|
|
162
|
+
newIndex,
|
|
163
|
+
value,
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async update(): Promise<void> {
|
|
170
|
+
await super.update();
|
|
171
|
+
this.updateControls();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
updateControls() {
|
|
175
|
+
if (!this.controlsElement) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
180
|
+
const reversed = arrayDirection.endsWith("reverse");
|
|
181
|
+
|
|
182
|
+
this.controlsElement.arrayDirection = arrayDirection;
|
|
183
|
+
|
|
184
|
+
if (arrayDirection.startsWith("column")) {
|
|
185
|
+
this.controlsElement.moveBackwardText = "up";
|
|
186
|
+
this.controlsElement.moveForwardText = "down";
|
|
187
|
+
} else {
|
|
188
|
+
this.controlsElement.moveBackwardText = "left";
|
|
189
|
+
this.controlsElement.moveForwardText = "right";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (reversed) {
|
|
193
|
+
this.controlsElement.disableMoveBackward =
|
|
194
|
+
Number(this.element.dataset.prop) ===
|
|
195
|
+
Number(this.element.dataset.length) - 1;
|
|
196
|
+
this.controlsElement.disableMoveForward =
|
|
197
|
+
Number(this.element.dataset.prop) === 0;
|
|
198
|
+
} else {
|
|
199
|
+
this.controlsElement.disableMoveBackward =
|
|
200
|
+
Number(this.element.dataset.prop) === 0;
|
|
201
|
+
this.controlsElement.disableMoveForward =
|
|
202
|
+
Number(this.element.dataset.prop) ===
|
|
203
|
+
Number(this.element.dataset.length) - 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
mount(): void {
|
|
208
|
+
if (!this.controlsElement) {
|
|
209
|
+
this.controlsElement = document.createElement(
|
|
210
|
+
"editable-array-item-controls",
|
|
211
|
+
);
|
|
212
|
+
this.controlsElement.addEventListener("edit", (e: any) => {
|
|
213
|
+
this.dispatchEdit(this.element.dataset.prop);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
this.controlsElement.addEventListener("move-backward", () => {
|
|
217
|
+
const fromIndex = Number(this.element.dataset.prop);
|
|
218
|
+
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
219
|
+
const reversed = arrayDirection.endsWith("reverse");
|
|
220
|
+
|
|
221
|
+
this.dispatchArrayMove(
|
|
222
|
+
fromIndex,
|
|
223
|
+
reversed ? fromIndex + 1 : fromIndex - 1,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (isEditableArrayItem(this.element.previousElementSibling)) {
|
|
227
|
+
this.element.previousElementSibling?.before(this.element);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
this.controlsElement.addEventListener("move-forward", () => {
|
|
232
|
+
const fromIndex = Number(this.element.dataset.prop);
|
|
233
|
+
const arrayDirection = this.parent?.arrayDirection ?? "column";
|
|
234
|
+
const reversed = arrayDirection.endsWith("reverse");
|
|
235
|
+
|
|
236
|
+
this.dispatchArrayMove(
|
|
237
|
+
fromIndex,
|
|
238
|
+
reversed ? fromIndex - 1 : fromIndex + 1,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (isEditableArrayItem(this.element.nextElementSibling)) {
|
|
242
|
+
this.element.nextElementSibling?.after(this.element);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
this.controlsElement.addEventListener("delete", () => {
|
|
247
|
+
this.dispatchArrayRemove(Number(this.element.dataset.prop));
|
|
248
|
+
this.element.remove();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.controlsElement.addEventListener("dragstart", (e: DragEvent) => {
|
|
252
|
+
const source = this.parent?.resolveSource();
|
|
253
|
+
if (!source || !e.dataTransfer || !this.element.dataset.prop) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const clientRect = this.element.getBoundingClientRect();
|
|
258
|
+
|
|
259
|
+
e.stopPropagation();
|
|
260
|
+
this.element.classList.add("dragging");
|
|
261
|
+
|
|
262
|
+
e.dataTransfer.setDragImage(this.element, clientRect.width - 35, 35);
|
|
263
|
+
e.dataTransfer.effectAllowed = "move";
|
|
264
|
+
e.dataTransfer?.setData(source, this.element.dataset.prop);
|
|
265
|
+
|
|
266
|
+
const id = Math.random().toString(36).slice(2);
|
|
267
|
+
this.element.id = id;
|
|
268
|
+
|
|
269
|
+
const data: Record<string, any> = {
|
|
270
|
+
index: this.element.dataset.prop,
|
|
271
|
+
sourceId: id,
|
|
272
|
+
value: this.value,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (this.inputConfig?.options?.structures?.values?.length > 0) {
|
|
276
|
+
data.structure = CloudCannon.findStructure(
|
|
277
|
+
this.inputConfig?.options?.structures,
|
|
278
|
+
this.value,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
e.dataTransfer?.setData(this.getDragType(), JSON.stringify(data));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
this.updateControls();
|
|
286
|
+
|
|
287
|
+
this.dispatchGetInputConfig().then((inputConfig) => {
|
|
288
|
+
if (!this.controlsElement) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (typeof inputConfig !== "object") {
|
|
293
|
+
this.element.append(this.controlsElement);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.controlsElement.disableReorder =
|
|
298
|
+
(inputConfig as any)?.options?.disable_reorder ?? false;
|
|
299
|
+
this.controlsElement.disableRemove =
|
|
300
|
+
(inputConfig as any)?.options?.disable_remove ?? false;
|
|
301
|
+
|
|
302
|
+
this.inputConfig = inputConfig;
|
|
303
|
+
this.element.append(this.controlsElement);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.element.ondragend = (): void => {
|
|
308
|
+
this.element.classList.remove("dragging");
|
|
309
|
+
this.element.style.boxShadow = "";
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
this.element.ondragenter = this.onHover.bind(this);
|
|
313
|
+
this.element.ondragover = this.onHover.bind(this);
|
|
314
|
+
|
|
315
|
+
this.element.ondragleave = (e: DragEvent): void => {
|
|
316
|
+
e.stopPropagation();
|
|
317
|
+
|
|
318
|
+
this.element.classList.remove("dragover");
|
|
319
|
+
this.element.style.boxShadow = "";
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
this.element.ondrop = (e: DragEvent): void => {
|
|
323
|
+
this.element.classList.remove("dragover");
|
|
324
|
+
this.element.style.boxShadow = "";
|
|
325
|
+
|
|
326
|
+
if (!e.dataTransfer) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const source = this.parent?.resolveSource();
|
|
331
|
+
if (!source) {
|
|
332
|
+
throw new Error("Source not found");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const dragType = this.getDragType();
|
|
336
|
+
const sameArrayData = e.dataTransfer.getData(source);
|
|
337
|
+
const otherArrayData = e.dataTransfer.getData(dragType);
|
|
338
|
+
|
|
339
|
+
const position = this.getDragPosition(e);
|
|
340
|
+
let newIndex =
|
|
341
|
+
position === "after"
|
|
342
|
+
? Number(this.element.dataset.prop) + 1
|
|
343
|
+
: Number(this.element.dataset.prop);
|
|
344
|
+
|
|
345
|
+
if (sameArrayData) {
|
|
346
|
+
const fromIndex = Number(sameArrayData);
|
|
347
|
+
if (fromIndex < newIndex) {
|
|
348
|
+
newIndex -= 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
e.stopPropagation();
|
|
353
|
+
e.dataTransfer.dropEffect = "move";
|
|
354
|
+
|
|
355
|
+
if (fromIndex !== newIndex) {
|
|
356
|
+
this.dispatchArrayMove(fromIndex, newIndex);
|
|
357
|
+
const sourceElement = this.parent?.element.querySelector(
|
|
358
|
+
`[data-prop="${fromIndex}"]`,
|
|
359
|
+
);
|
|
360
|
+
if (sourceElement) {
|
|
361
|
+
if (position === "after") {
|
|
362
|
+
this.element.after(sourceElement);
|
|
363
|
+
} else {
|
|
364
|
+
this.element.before(sourceElement);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else if (otherArrayData) {
|
|
369
|
+
const { index, sourceId, value, structure } =
|
|
370
|
+
JSON.parse(otherArrayData);
|
|
371
|
+
if (dragType === "cc:structure") {
|
|
372
|
+
if (!this.inputConfig?.options?.structures?.values) {
|
|
373
|
+
throw new Error("No structures found");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const targetStructure = CloudCannon.findStructure(
|
|
377
|
+
this.inputConfig.options.structures,
|
|
378
|
+
this.value,
|
|
379
|
+
);
|
|
380
|
+
if (!targetStructure) {
|
|
381
|
+
throw new Error("No target structure found");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (JSON.stringify(structure) !== JSON.stringify(targetStructure)) {
|
|
385
|
+
throw new Error("Structures do not match");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const sourceElement = document.getElementById(sourceId);
|
|
390
|
+
if (sourceElement && hasEditableArrayItem(sourceElement)) {
|
|
391
|
+
if (Array.isArray(sourceElement.editable.parent?.value)) {
|
|
392
|
+
sourceElement.editable.parent.value = structuredClone(
|
|
393
|
+
sourceElement.editable.parent.value,
|
|
394
|
+
);
|
|
395
|
+
sourceElement.editable.parent.value.splice(index, 1);
|
|
396
|
+
}
|
|
397
|
+
sourceElement.editable.dispatchArrayRemove(index);
|
|
398
|
+
if (position === "after") {
|
|
399
|
+
this.element.after(sourceElement);
|
|
400
|
+
} else {
|
|
401
|
+
this.element.before(sourceElement);
|
|
402
|
+
}
|
|
403
|
+
sourceElement.editable.parent?.update();
|
|
404
|
+
}
|
|
405
|
+
if (Array.isArray(this.parent?.value)) {
|
|
406
|
+
this.parent.value = structuredClone(this.parent.value);
|
|
407
|
+
this.parent.value.splice(newIndex, 0, value);
|
|
408
|
+
}
|
|
409
|
+
this.dispatchArrayAdd(newIndex, value);
|
|
410
|
+
this.parent?.update();
|
|
411
|
+
|
|
412
|
+
e.preventDefault();
|
|
413
|
+
e.stopPropagation();
|
|
414
|
+
e.dataTransfer.dropEffect = "move";
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
if (this.value !== undefined) {
|
|
419
|
+
this.update();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
setupListeners(): void {
|
|
424
|
+
super.setupListeners();
|
|
425
|
+
this.parent?.registerListener({ editable: this });
|
|
426
|
+
}
|
|
427
|
+
}
|