@cloudcannon/editable-regions 0.0.8 → 0.0.10
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/components/editable-snippet-component.ts +1 -1
- package/components/index.ts +0 -13
- package/helpers/checks.ts +9 -36
- package/helpers/cloudcannon.d.mts +20 -0
- package/helpers/cloudcannon.mjs +10 -0
- package/helpers/hydrate-editable-regions.ts +7 -1
- package/integrations/astro/astro-integration.mjs +10 -54
- package/integrations/astro/modules/content.js +7 -1
- package/integrations/astro/modules/secrets.js +4 -0
- package/nodes/editable-array-item.ts +9 -9
- package/nodes/editable-array.ts +74 -28
- package/nodes/editable-component.ts +78 -15
- package/nodes/editable-source.ts +8 -2
- package/nodes/editable.ts +197 -33
- package/package.json +2 -2
- package/helpers/loading.ts +0 -7
- package/integrations/astro/modules/actions.js +0 -74
- package/integrations/astro/modules/client-router.astro +0 -5
- package/integrations/astro/modules/i18n.js +0 -76
- package/integrations/astro/modules/middleware.js +0 -27
- package/integrations/astro/modules/transitions.js +0 -63
|
@@ -9,7 +9,7 @@ export default class EditableSnippetComponent extends HTMLElement {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
set snippetData(value: unknown) {
|
|
12
|
-
this.editable.pushValue(value);
|
|
12
|
+
this.editable.pushValue(value, {}, { path: "", editable: this.editable });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
connectedCallback(): void {
|
package/components/index.ts
CHANGED
|
@@ -6,12 +6,10 @@ export { default as EditableSnippetComponent } from "./editable-snippet-componen
|
|
|
6
6
|
export { default as EditableSourceComponent } from "./editable-source-component.js";
|
|
7
7
|
export { default as EditableTextComponent } from "./editable-text-component.js";
|
|
8
8
|
|
|
9
|
-
import { apiLoadedPromise } from "../helpers/cloudcannon.mjs";
|
|
10
9
|
import {
|
|
11
10
|
dehydrateDataEditableRegions,
|
|
12
11
|
hydrateDataEditableRegions,
|
|
13
12
|
} from "../helpers/hydrate-editable-regions";
|
|
14
|
-
import { completeLoading } from "../helpers/loading.js";
|
|
15
13
|
|
|
16
14
|
const observer = new MutationObserver((mutations) => {
|
|
17
15
|
mutations.forEach((mutation) => {
|
|
@@ -32,14 +30,3 @@ const observer = new MutationObserver((mutations) => {
|
|
|
32
30
|
hydrateDataEditableRegions(document.body);
|
|
33
31
|
|
|
34
32
|
observer.observe(document, { childList: true, subtree: true });
|
|
35
|
-
|
|
36
|
-
Promise.all([
|
|
37
|
-
apiLoadedPromise,
|
|
38
|
-
customElements.whenDefined("editable-array-item"),
|
|
39
|
-
customElements.whenDefined("editable-array"),
|
|
40
|
-
customElements.whenDefined("editable-text"),
|
|
41
|
-
customElements.whenDefined("editable-component"),
|
|
42
|
-
customElements.whenDefined("editable-image"),
|
|
43
|
-
customElements.whenDefined("editable-source"),
|
|
44
|
-
customElements.whenDefined("editable-snippet"),
|
|
45
|
-
]).then(() => completeLoading());
|
package/helpers/checks.ts
CHANGED
|
@@ -2,32 +2,11 @@ import Editable from "../nodes/editable.js";
|
|
|
2
2
|
import EditableArrayItem from "../nodes/editable-array-item.js";
|
|
3
3
|
import EditableText from "../nodes/editable-text.js";
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
"EDITABLE-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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",
|
|
5
|
+
const getEditableType = (el: HTMLElement): string | undefined => {
|
|
6
|
+
if (el.tagName.startsWith("EDITABLE-")) {
|
|
7
|
+
return el.tagName.slice(9).toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
return el.dataset.editable;
|
|
31
10
|
};
|
|
32
11
|
|
|
33
12
|
export const hasEditable = <T extends object>(
|
|
@@ -53,7 +32,7 @@ export const isEditableWebcomponent = (el: unknown): boolean => {
|
|
|
53
32
|
return false;
|
|
54
33
|
}
|
|
55
34
|
|
|
56
|
-
return
|
|
35
|
+
return el.tagName.startsWith("EDITABLE-");
|
|
57
36
|
};
|
|
58
37
|
|
|
59
38
|
export const isEditableElement = (el: unknown): boolean => {
|
|
@@ -62,19 +41,13 @@ export const isEditableElement = (el: unknown): boolean => {
|
|
|
62
41
|
}
|
|
63
42
|
|
|
64
43
|
return (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
EDITABLE_REGION_TYPES.includes(el.dataset.editable))
|
|
44
|
+
el.tagName.startsWith("EDITABLE-") ||
|
|
45
|
+
typeof el.dataset.editable === "string"
|
|
68
46
|
);
|
|
69
47
|
};
|
|
70
48
|
|
|
71
49
|
export const areEqualEditables = (a: HTMLElement, b: HTMLElement) => {
|
|
72
|
-
if (
|
|
73
|
-
a.tagName !== b.tagName &&
|
|
74
|
-
a.dataset.editable !== b.dataset.editable &&
|
|
75
|
-
CORRESPONDING_NAME[a.tagName] !== b.dataset.editable &&
|
|
76
|
-
CORRESPONDING_NAME[b.tagName] !== a.dataset.editable
|
|
77
|
-
) {
|
|
50
|
+
if (getEditableType(a) !== getEditableType(b)) {
|
|
78
51
|
return false;
|
|
79
52
|
}
|
|
80
53
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise that resolves when the CloudCannon API is loaded
|
|
3
|
+
* @type {Promise<void>}
|
|
4
|
+
*/
|
|
5
|
+
export const apiLoadedPromise: Promise<void>;
|
|
6
|
+
export function addEditableComponentRenderer(key: string, renderer: ComponentRenderer): void;
|
|
7
|
+
export function addEditableSnippetRenderer(key: string, renderer: ComponentRenderer): void;
|
|
8
|
+
export function getEditableComponentRenderers(): Record<string, ComponentRenderer>;
|
|
9
|
+
export function getEditableSnippetRenderers(): Record<string, ComponentRenderer>;
|
|
10
|
+
export function realizeAPIValue(value: unknown): Promise<unknown>;
|
|
11
|
+
export { _cloudcannon as CloudCannon };
|
|
12
|
+
export type CloudCannonEditorWindow = import("@cloudcannon/javascript-api").CloudCannonEditorWindow;
|
|
13
|
+
export type CloudCannonJavaScriptV1API = import("@cloudcannon/javascript-api").CloudCannonJavaScriptV1API;
|
|
14
|
+
export type ComponentRenderer = (props: any) => HTMLElement | Promise<HTMLElement>;
|
|
15
|
+
export type ExtendedWindow = CloudCannonEditorWindow & {
|
|
16
|
+
cc_components?: Record<string, ComponentRenderer>;
|
|
17
|
+
cc_snippets?: Record<string, ComponentRenderer>;
|
|
18
|
+
};
|
|
19
|
+
/** @type {CloudCannonJavaScriptV1API} */
|
|
20
|
+
declare let _cloudcannon: CloudCannonJavaScriptV1API;
|
package/helpers/cloudcannon.mjs
CHANGED
|
@@ -107,4 +107,14 @@ export const realizeAPIValue = async (value) => {
|
|
|
107
107
|
return value;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
export const addCustomEditableRegion = (key, region) => {
|
|
111
|
+
extendedWindow.editableRegionMap ??= {};
|
|
112
|
+
extendedWindow.editableRegionMap[key] = region;
|
|
113
|
+
extendedWindow.hydrateDataEditableRegions(document.body);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const getCustomEditableRegions = () => {
|
|
117
|
+
return extendedWindow.editableRegionMap ?? {};
|
|
118
|
+
};
|
|
119
|
+
|
|
110
120
|
export { _cloudcannon as CloudCannon };
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../nodes";
|
|
10
10
|
import { hasEditable, isEditableWebcomponent } from "./checks";
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const baseEditableMap: Record<string, typeof Editable | undefined> = {
|
|
13
13
|
array: EditableArray,
|
|
14
14
|
"array-item": EditableArrayItem,
|
|
15
15
|
component: EditableComponent,
|
|
@@ -31,6 +31,10 @@ export const dehydrateDataEditableRegions = (root: Element) => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export const hydrateDataEditableRegions = (root: Element) => {
|
|
34
|
+
const editableMap: Record<string, typeof Editable | undefined> = {
|
|
35
|
+
...baseEditableMap,
|
|
36
|
+
...((window as any).editableRegionMap ?? {}),
|
|
37
|
+
};
|
|
34
38
|
if (
|
|
35
39
|
root instanceof HTMLElement &&
|
|
36
40
|
root.dataset.editable &&
|
|
@@ -89,3 +93,5 @@ export const hydrateDataEditableRegions = (root: Element) => {
|
|
|
89
93
|
}
|
|
90
94
|
});
|
|
91
95
|
};
|
|
96
|
+
|
|
97
|
+
(window as any).hydrateDataEditableRegions = hydrateDataEditableRegions;
|
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"assets",
|
|
7
|
-
"content",
|
|
8
|
-
"i18n",
|
|
9
|
-
"middleware",
|
|
10
|
-
"transitions",
|
|
11
|
-
];
|
|
4
|
+
/** @type{string[]} */
|
|
5
|
+
const SUPPORTED_VIRTUAL_MODULES = ["assets", "content"];
|
|
12
6
|
|
|
13
7
|
/**
|
|
14
8
|
* @return {import("astro").AstroIntegration}
|
|
15
9
|
*/
|
|
16
10
|
export default () => {
|
|
17
|
-
/** @type {import("astro").AstroConfig} */
|
|
18
|
-
let astroConfig;
|
|
19
|
-
|
|
20
11
|
return {
|
|
21
12
|
name: "editable-regions",
|
|
22
13
|
hooks: {
|
|
23
|
-
"astro:config:setup": ({
|
|
14
|
+
"astro:config:setup": ({ updateConfig }) => {
|
|
24
15
|
updateConfig({
|
|
25
16
|
vite: {
|
|
26
17
|
define: {
|
|
@@ -28,7 +19,6 @@ export default () => {
|
|
|
28
19
|
},
|
|
29
20
|
},
|
|
30
21
|
});
|
|
31
|
-
astroConfig = config;
|
|
32
22
|
},
|
|
33
23
|
"astro:build:setup": async ({ target, vite }) => {
|
|
34
24
|
if (target === "client") {
|
|
@@ -68,14 +58,6 @@ export default () => {
|
|
|
68
58
|
.replace("/client", "")
|
|
69
59
|
.replace("/server", "");
|
|
70
60
|
|
|
71
|
-
if (type === "env") {
|
|
72
|
-
return "\0editable-region:env";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!SUPPORTED_VIRTUAL_MODULES.includes(type)) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
61
|
let dir = "";
|
|
80
62
|
if (typeof __dirname !== "undefined") {
|
|
81
63
|
dir = __dirname;
|
|
@@ -83,41 +65,15 @@ export default () => {
|
|
|
83
65
|
dir = dirname(fileURLToPath(import.meta.url));
|
|
84
66
|
}
|
|
85
67
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
if (type === "env" && id.endsWith("/server")) {
|
|
69
|
+
return join(dir, "modules", "secrets.js");
|
|
70
|
+
}
|
|
89
71
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Object.entries(astroConfig?.env?.schema ?? {}).forEach(
|
|
94
|
-
([key, schema]) => {
|
|
95
|
-
if (
|
|
96
|
-
schema.context !== "client" ||
|
|
97
|
-
schema.access !== "public"
|
|
98
|
-
) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
72
|
+
if (!SUPPORTED_VIRTUAL_MODULES.includes(type)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
switch (schema.type) {
|
|
104
|
-
case "boolean":
|
|
105
|
-
contents += `export const ${key} = ${!!process.env[key]};\n`;
|
|
106
|
-
break;
|
|
107
|
-
case "number":
|
|
108
|
-
contents += `export const ${key} = ${Number(process.env[key])};\n`;
|
|
109
|
-
break;
|
|
110
|
-
default:
|
|
111
|
-
contents += `export const ${key} = ${JSON.stringify(process.env[key] ?? "")};\n`;
|
|
112
|
-
}
|
|
113
|
-
} catch (_e) {
|
|
114
|
-
//Error intentionally ignored
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
contents +=
|
|
119
|
-
'export const getSecret = () => console.log("[CloudCannon] getSecret is not supported in an editable component. Please use an editing fallback instead.");';
|
|
120
|
-
return contents;
|
|
76
|
+
return join(dir, "modules", `${type}.js`);
|
|
121
77
|
}
|
|
122
78
|
},
|
|
123
79
|
});
|
|
@@ -26,12 +26,18 @@ export const getCollection = async (collectionKey, filter) => {
|
|
|
26
26
|
slug = data.slug;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
const body = await file.get();
|
|
29
30
|
return {
|
|
30
31
|
collection: collectionKey,
|
|
31
32
|
id: id,
|
|
32
33
|
data: data,
|
|
33
34
|
slug: slug,
|
|
34
|
-
body:
|
|
35
|
+
body: body,
|
|
36
|
+
render: () => ({
|
|
37
|
+
Content: () => body ?? "Content is not available when live editing",
|
|
38
|
+
headings: [],
|
|
39
|
+
remarkPluginFrontmatter: {},
|
|
40
|
+
}),
|
|
35
41
|
};
|
|
36
42
|
});
|
|
37
43
|
|
|
@@ -249,8 +249,8 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
249
249
|
);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
async update(): Promise<void> {
|
|
253
|
-
await super.update();
|
|
252
|
+
async update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
253
|
+
await super.update(partialSubtree);
|
|
254
254
|
this.updateControls();
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -485,12 +485,12 @@ export default class EditableArrayItem extends EditableComponent {
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
488
|
+
getSpecialProps(
|
|
489
|
+
incomingSpecialProps: Record<string, unknown> = {},
|
|
490
|
+
): Record<string, unknown> {
|
|
491
|
+
return {
|
|
492
|
+
...super.getSpecialProps(incomingSpecialProps),
|
|
493
|
+
"@index": Number(this.element.dataset.prop),
|
|
494
|
+
};
|
|
495
495
|
}
|
|
496
496
|
}
|
package/nodes/editable-array.ts
CHANGED
|
@@ -37,13 +37,16 @@ export default class EditableArray extends Editable {
|
|
|
37
37
|
private updatePromise: Promise<void> | undefined;
|
|
38
38
|
private needsReupdate = false;
|
|
39
39
|
private addButton?: EditableRegionButton;
|
|
40
|
+
private pendingPartialSubtree?: ChildNode | null;
|
|
40
41
|
|
|
41
42
|
async registerListener(listener: EditableListener): Promise<void> {
|
|
42
43
|
if (!this.value) {
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const __base_context = {
|
|
47
|
+
const __base_context = {
|
|
48
|
+
...this.contextBase,
|
|
49
|
+
};
|
|
47
50
|
let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
|
|
48
51
|
if (CloudCannon.isAPICollection(this.value)) {
|
|
49
52
|
value = await this.value.items();
|
|
@@ -90,7 +93,7 @@ export default class EditableArray extends Editable {
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
if (listener.path) {
|
|
93
|
-
listener.editable.pushValue(value, listener, {
|
|
96
|
+
listener.editable.pushValue(value, this.specialProps, listener, {
|
|
94
97
|
__base_context,
|
|
95
98
|
});
|
|
96
99
|
}
|
|
@@ -147,24 +150,29 @@ export default class EditableArray extends Editable {
|
|
|
147
150
|
return value;
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
update(): Promise<void> {
|
|
153
|
+
update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
151
154
|
if (this.updatePromise) {
|
|
152
155
|
this.needsReupdate = true;
|
|
156
|
+
this.pendingPartialSubtree = partialSubtree;
|
|
153
157
|
return this.updatePromise;
|
|
154
158
|
}
|
|
155
|
-
this.updatePromise = this._update().then(() => {
|
|
159
|
+
this.updatePromise = this._update(partialSubtree).then(() => {
|
|
156
160
|
this.updatePromise = undefined;
|
|
157
161
|
if (this.needsReupdate) {
|
|
158
162
|
this.needsReupdate = false;
|
|
159
|
-
|
|
163
|
+
const savedPartialSubtree = this.pendingPartialSubtree;
|
|
164
|
+
this.pendingPartialSubtree = undefined;
|
|
165
|
+
return this.update(savedPartialSubtree);
|
|
160
166
|
}
|
|
161
167
|
});
|
|
162
168
|
return this.updatePromise;
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
private async _update(): Promise<void> {
|
|
171
|
+
private async _update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
166
172
|
let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
|
|
167
|
-
const __base_context = {
|
|
173
|
+
const __base_context = {
|
|
174
|
+
...this.contextBase,
|
|
175
|
+
};
|
|
168
176
|
if (CloudCannon.isAPICollection(this.value)) {
|
|
169
177
|
value = await this.value.items();
|
|
170
178
|
} else if (CloudCannon.isAPIDataset(this.value)) {
|
|
@@ -186,6 +194,24 @@ export default class EditableArray extends Editable {
|
|
|
186
194
|
value = [];
|
|
187
195
|
}
|
|
188
196
|
|
|
197
|
+
const partialChildren: (HTMLElement & { editable?: EditableArrayItem })[] =
|
|
198
|
+
[];
|
|
199
|
+
if (partialSubtree instanceof HTMLElement) {
|
|
200
|
+
for (const child of partialSubtree.querySelectorAll(
|
|
201
|
+
"editable-array-item,[data-editable='array-item']",
|
|
202
|
+
)) {
|
|
203
|
+
let parent = child.parentElement;
|
|
204
|
+
while (parent instanceof HTMLElement && !isEditableElement(parent)) {
|
|
205
|
+
parent = parent.parentElement;
|
|
206
|
+
}
|
|
207
|
+
if (parent !== partialSubtree) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
partialChildren.push(child as any);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
189
215
|
const templates: {
|
|
190
216
|
keyed: Record<
|
|
191
217
|
string,
|
|
@@ -241,23 +267,6 @@ export default class EditableArray extends Editable {
|
|
|
241
267
|
children.push(child as any);
|
|
242
268
|
}
|
|
243
269
|
|
|
244
|
-
if (
|
|
245
|
-
children.length === 0 &&
|
|
246
|
-
!this.element.dataset.component &&
|
|
247
|
-
!this.element.dataset.componentKey &&
|
|
248
|
-
!templates.unkeyed &&
|
|
249
|
-
Object.keys(templates.keyed).length === 0
|
|
250
|
-
) {
|
|
251
|
-
const error = document.createElement("editable-region-error-card");
|
|
252
|
-
error.setAttribute("heading", "Failed to render array editable region");
|
|
253
|
-
error.setAttribute(
|
|
254
|
-
"message",
|
|
255
|
-
"Array editable regions with no child array items must have either a 'data-component' attribute or a 'data-component-key' attribute. Please add an item to this array then save and rebuild to see your changes or add a 'data-component' or 'data-component-key' attribute to this element.",
|
|
256
|
-
);
|
|
257
|
-
this.element.replaceChildren(error);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
270
|
const key = this.element.dataset.idKey ?? this.element.dataset.componentKey;
|
|
262
271
|
|
|
263
272
|
if (!key) {
|
|
@@ -274,8 +283,14 @@ export default class EditableArray extends Editable {
|
|
|
274
283
|
|
|
275
284
|
for (let i = 0; i < value.length; i++) {
|
|
276
285
|
let child = children[i];
|
|
286
|
+
const partialChild = partialChildren[i];
|
|
287
|
+
|
|
277
288
|
if (!child) {
|
|
278
|
-
if (
|
|
289
|
+
if (partialChild) {
|
|
290
|
+
child = partialChild.cloneNode(true) as HTMLElement & {
|
|
291
|
+
editable?: EditableArrayItem;
|
|
292
|
+
};
|
|
293
|
+
} else if (templates.unkeyed) {
|
|
279
294
|
child = templates.unkeyed.cloneNode(true) as HTMLElement & {
|
|
280
295
|
editable?: EditableArrayItem;
|
|
281
296
|
};
|
|
@@ -284,11 +299,22 @@ export default class EditableArray extends Editable {
|
|
|
284
299
|
child = document.createElement(
|
|
285
300
|
"editable-array-item",
|
|
286
301
|
) as EditableArrayItemComponent;
|
|
287
|
-
} else {
|
|
288
|
-
// Empty arrays should be caught by the error case above so children[0] should always exist
|
|
302
|
+
} else if (children[0]) {
|
|
289
303
|
child = children[0].cloneNode(true) as HTMLElement & {
|
|
290
304
|
editable?: EditableArrayItem;
|
|
291
305
|
};
|
|
306
|
+
} else {
|
|
307
|
+
const error = document.createElement("editable-region-error-card");
|
|
308
|
+
error.setAttribute(
|
|
309
|
+
"heading",
|
|
310
|
+
"Failed to render array editable region",
|
|
311
|
+
);
|
|
312
|
+
error.setAttribute(
|
|
313
|
+
"message",
|
|
314
|
+
"Array editable region contains an array item that cannot be rendered. Either a 'data-component' attribute or a 'data-component-key' attribute is required to render array items that weren't previously on the page. Please save and rebuild to see your changes or add a 'data-component' or 'data-component-key' attribute to this element.",
|
|
315
|
+
);
|
|
316
|
+
this.element.replaceChildren(error);
|
|
317
|
+
return;
|
|
292
318
|
}
|
|
293
319
|
this.element.appendChild(child);
|
|
294
320
|
}
|
|
@@ -300,8 +326,10 @@ export default class EditableArray extends Editable {
|
|
|
300
326
|
child.dataset.length = `${children.length}`;
|
|
301
327
|
child.editable?.pushValue(
|
|
302
328
|
value,
|
|
329
|
+
this.specialProps,
|
|
303
330
|
{ path: `${i}`, editable: child.editable },
|
|
304
331
|
{ __base_context },
|
|
332
|
+
partialChild,
|
|
305
333
|
);
|
|
306
334
|
}
|
|
307
335
|
return;
|
|
@@ -351,8 +379,10 @@ export default class EditableArray extends Editable {
|
|
|
351
379
|
|
|
352
380
|
child.editable?.pushValue(
|
|
353
381
|
value,
|
|
382
|
+
this.specialProps,
|
|
354
383
|
{ path: `${index}`, editable: child.editable },
|
|
355
384
|
{ __base_context },
|
|
385
|
+
partialChildren[index],
|
|
356
386
|
);
|
|
357
387
|
});
|
|
358
388
|
|
|
@@ -380,6 +410,7 @@ export default class EditableArray extends Editable {
|
|
|
380
410
|
const existingElement = children[i];
|
|
381
411
|
const templateElement = templates.keyed[key] ?? templates.unkeyed;
|
|
382
412
|
const componentKey = componentKeys[i] || this.element.dataset.component;
|
|
413
|
+
const partialChild = partialChildren[i];
|
|
383
414
|
|
|
384
415
|
const matchingChildIndex = children.findIndex(
|
|
385
416
|
(child, i) => child.dataset.id === key && !moved[i],
|
|
@@ -388,7 +419,9 @@ export default class EditableArray extends Editable {
|
|
|
388
419
|
let matchingChild = children[matchingChildIndex];
|
|
389
420
|
if (!matchingChild) {
|
|
390
421
|
const clone = children.find((child) => child.dataset.id === key);
|
|
391
|
-
if (
|
|
422
|
+
if (partialChild) {
|
|
423
|
+
matchingChild = partialChild.cloneNode(true) as any;
|
|
424
|
+
} else if (templateElement) {
|
|
392
425
|
matchingChild = templateElement.cloneNode(true) as any;
|
|
393
426
|
matchingChild.dataset.editable = "array-item";
|
|
394
427
|
} else if (componentKey) {
|
|
@@ -448,8 +481,10 @@ export default class EditableArray extends Editable {
|
|
|
448
481
|
matchingChild.editable.parent = this;
|
|
449
482
|
matchingChild.editable.pushValue(
|
|
450
483
|
value,
|
|
484
|
+
this.specialProps,
|
|
451
485
|
{ path: `${i}`, editable: matchingChild.editable },
|
|
452
486
|
{ __base_context },
|
|
487
|
+
partialChild,
|
|
453
488
|
);
|
|
454
489
|
}
|
|
455
490
|
});
|
|
@@ -502,4 +537,15 @@ export default class EditableArray extends Editable {
|
|
|
502
537
|
);
|
|
503
538
|
});
|
|
504
539
|
}
|
|
540
|
+
|
|
541
|
+
getSpecialProps(
|
|
542
|
+
incomingSpecialProps: Record<string, unknown> = {},
|
|
543
|
+
): Record<string, unknown> {
|
|
544
|
+
return {
|
|
545
|
+
...super.getSpecialProps(incomingSpecialProps),
|
|
546
|
+
"@length": Array.isArray(this.propsBase)
|
|
547
|
+
? this.propsBase.length
|
|
548
|
+
: undefined,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
505
551
|
}
|