@cloudcannon/editable-regions 0.0.9 → 0.0.11
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/components/ui/editable-array-item-controls.ts +9 -0
- package/components/ui/editable-component-controls.ts +67 -11
- package/helpers/checks.ts +14 -37
- package/helpers/cloudcannon.mjs +10 -2
- package/helpers/hydrate-editable-regions.ts +7 -1
- package/integrations/astro/astro-integration.mjs +109 -100
- package/integrations/astro/index.mjs +21 -8
- 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 +155 -97
- package/nodes/editable-source.ts +8 -2
- package/nodes/editable.ts +197 -33
- package/package.json +3 -3
- package/styles/editable-array-item.css +5 -3
- package/styles/editable-component.css +5 -3
- package/styles/ui/editable-component-controls.css +21 -0
- 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
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
|
}
|
|
@@ -19,6 +19,7 @@ export default class EditableComponent extends Editable {
|
|
|
19
19
|
|
|
20
20
|
private updatePromise: Promise<void> | undefined;
|
|
21
21
|
private needsReupdate = false;
|
|
22
|
+
private pendingPartialSubtree?: ChildNode | null;
|
|
22
23
|
|
|
23
24
|
getComponents() {
|
|
24
25
|
return getEditableComponentRenderers();
|
|
@@ -51,31 +52,81 @@ export default class EditableComponent extends Editable {
|
|
|
51
52
|
return true;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
update(): Promise<void> {
|
|
55
|
+
update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
55
56
|
if (this.updatePromise) {
|
|
56
57
|
this.needsReupdate = true;
|
|
58
|
+
this.pendingPartialSubtree = partialSubtree;
|
|
57
59
|
return this.updatePromise;
|
|
58
60
|
}
|
|
59
|
-
this.updatePromise = this._update().then(() => {
|
|
61
|
+
this.updatePromise = this._update(partialSubtree).then(() => {
|
|
60
62
|
this.updatePromise = undefined;
|
|
61
63
|
if (this.needsReupdate) {
|
|
62
64
|
this.needsReupdate = false;
|
|
63
|
-
|
|
65
|
+
const savedPartialSubtree = this.pendingPartialSubtree;
|
|
66
|
+
this.pendingPartialSubtree = undefined;
|
|
67
|
+
return this.update(savedPartialSubtree);
|
|
64
68
|
}
|
|
65
69
|
});
|
|
66
70
|
return this.updatePromise;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
santiseComponentOutput(el: HTMLElement): HTMLElement {
|
|
74
|
+
el.querySelectorAll("noscript").forEach((el) => el.remove());
|
|
75
|
+
return el;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async _update(partialSubtree?: ChildNode | null): Promise<void> {
|
|
79
|
+
if (partialSubtree) {
|
|
80
|
+
if (this.controlsElement) {
|
|
81
|
+
this.controlsElement.remove();
|
|
82
|
+
}
|
|
83
|
+
this.updateTree(this.element, partialSubtree);
|
|
84
|
+
if (this.controlsElement) {
|
|
85
|
+
this.element.appendChild(this.controlsElement);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
this.element.classList.remove("errored");
|
|
71
91
|
|
|
72
92
|
const key = this.element.dataset.component;
|
|
73
93
|
if (!key) {
|
|
74
94
|
return super.update();
|
|
75
95
|
}
|
|
76
|
-
|
|
96
|
+
|
|
97
|
+
let component = this.getComponents()?.[key];
|
|
98
|
+
for (let i = 0; !component && i < 20; i++) {
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
100
|
+
component = this.getComponents()?.[key];
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
if (!component) {
|
|
78
|
-
|
|
104
|
+
this.element.classList.add("errored");
|
|
105
|
+
const error = document.createElement("editable-region-error-card");
|
|
106
|
+
error.setAttribute("heading", "Failed to render component");
|
|
107
|
+
error.setAttribute(
|
|
108
|
+
"message",
|
|
109
|
+
`Failed to find a registered component with the key "${key}". This may mean that the provided "data-component" attribute is incorrect or that the component hasn't been registered.`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const caseMatch = Object.keys(this.getComponents()).find(
|
|
113
|
+
(k) => k.toLowerCase() === key.toLowerCase(),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (Object.keys(this.getComponents()).length === 0) {
|
|
117
|
+
error.setAttribute(
|
|
118
|
+
"hint",
|
|
119
|
+
"There are no registered components currently available. Please check that you've included your registration script and that it's running correctly.",
|
|
120
|
+
);
|
|
121
|
+
} else if (caseMatch) {
|
|
122
|
+
error.setAttribute(
|
|
123
|
+
"hint",
|
|
124
|
+
`The component key "${key}" is not case-sensitive. Did you mean "${caseMatch}"?`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.element.replaceChildren(error);
|
|
129
|
+
return;
|
|
79
130
|
}
|
|
80
131
|
|
|
81
132
|
const value = await realizeAPIValue(this.value);
|
|
@@ -87,7 +138,7 @@ export default class EditableComponent extends Editable {
|
|
|
87
138
|
|
|
88
139
|
let rootEl: HTMLElement;
|
|
89
140
|
try {
|
|
90
|
-
rootEl = await component(value);
|
|
141
|
+
rootEl = this.santiseComponentOutput(await component(value));
|
|
91
142
|
} catch (err: unknown) {
|
|
92
143
|
this.element.classList.add("errored");
|
|
93
144
|
const error = document.createElement("editable-region-error-card");
|
|
@@ -120,94 +171,112 @@ export default class EditableComponent extends Editable {
|
|
|
120
171
|
targetNode?: ChildNode | null,
|
|
121
172
|
renderNode?: ChildNode | null,
|
|
122
173
|
): void {
|
|
123
|
-
let
|
|
174
|
+
let nextTargetChild: ChildNode | null | undefined =
|
|
124
175
|
targetNode?.firstChild ?? undefined;
|
|
125
|
-
let
|
|
176
|
+
let nextRenderChild: ChildNode | null | undefined =
|
|
126
177
|
renderNode?.firstChild ?? undefined;
|
|
127
|
-
while (renderChild || targetChild) {
|
|
128
|
-
const nextTargetChild: ChildNode | null | undefined =
|
|
129
|
-
targetChild?.nextSibling ?? undefined;
|
|
130
|
-
const nextRenderChild: ChildNode | null | undefined =
|
|
131
|
-
renderChild?.nextSibling ?? undefined;
|
|
132
178
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
179
|
+
while (nextRenderChild || nextTargetChild) {
|
|
180
|
+
const targetChild: ChildNode | null | undefined = nextTargetChild;
|
|
181
|
+
const renderChild: ChildNode | null | undefined = nextRenderChild;
|
|
182
|
+
|
|
183
|
+
nextTargetChild = targetChild?.nextSibling ?? undefined;
|
|
184
|
+
nextRenderChild = renderChild?.nextSibling ?? undefined;
|
|
185
|
+
|
|
186
|
+
// There is an existing node in the DOM but no rendered node in it's place
|
|
187
|
+
if (!renderChild && targetChild) {
|
|
188
|
+
targetNode?.removeChild(targetChild);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// There is a node to be rendered, but no existing node in the DOM in it's place
|
|
193
|
+
if (renderChild && !targetChild) {
|
|
194
|
+
targetNode?.appendChild(renderChild);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!targetChild || !renderChild) {
|
|
199
|
+
throw new Error("Illegal rendering state, both children should exist ");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The existing child is an element and the rendered child is a node (i.e. some text)
|
|
203
|
+
if (targetChild instanceof Element && !(renderChild instanceof Element)) {
|
|
204
|
+
// Render the (text) node before the element and skip to the next rendered child
|
|
138
205
|
targetChild.before(renderChild);
|
|
139
|
-
|
|
206
|
+
nextTargetChild = targetChild;
|
|
140
207
|
continue;
|
|
141
208
|
}
|
|
142
209
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
!(targetChild instanceof Element)
|
|
147
|
-
) {
|
|
210
|
+
// The existing child is a node (i.e. some text) and the rendered child is an element
|
|
211
|
+
if (renderChild instanceof Element && !(targetChild instanceof Element)) {
|
|
212
|
+
// Delete the (text) node and skip to the next existing child
|
|
148
213
|
targetChild.remove();
|
|
149
|
-
|
|
214
|
+
nextRenderChild = renderChild;
|
|
150
215
|
continue;
|
|
151
216
|
}
|
|
152
217
|
|
|
218
|
+
// Both existing and rendered children are nodes (i.e. some text)
|
|
153
219
|
if (
|
|
154
|
-
renderChild &&
|
|
155
|
-
targetChild &&
|
|
156
220
|
!(renderChild instanceof Element) &&
|
|
157
221
|
!(targetChild instanceof Element)
|
|
158
222
|
) {
|
|
223
|
+
// Render the offscreen node's content into the existing node.
|
|
159
224
|
targetChild.nodeValue = renderChild.nodeValue;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Both children are elements but are different types
|
|
229
|
+
if (renderChild.nodeName !== targetChild.nodeName) {
|
|
230
|
+
targetChild.replaceWith(renderChild);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Both existing and rendered children are the same kind of element, and neither is editable
|
|
235
|
+
if (!isEditableElement(renderChild) && !isEditableElement(targetChild)) {
|
|
236
|
+
// Update the existing element to match the rendered element and recurse their subtrees
|
|
237
|
+
this.updateNode(targetChild, renderChild);
|
|
238
|
+
this.updateTree(targetChild, renderChild);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
!(renderChild instanceof HTMLElement) ||
|
|
244
|
+
!(targetChild instanceof HTMLElement)
|
|
165
245
|
) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
} else if (hasEditable(targetChild)) {
|
|
190
|
-
this.updateEditable(renderChild, targetChild);
|
|
191
|
-
}
|
|
192
|
-
} else if (renderChild && targetChild) {
|
|
193
|
-
if (
|
|
194
|
-
renderChild.nodeName !== targetChild.nodeName ||
|
|
195
|
-
isEditableElement(renderChild) ||
|
|
196
|
-
isEditableElement(targetChild)
|
|
197
|
-
) {
|
|
198
|
-
targetChild.replaceWith(renderChild);
|
|
199
|
-
} else {
|
|
200
|
-
this.updateNode(targetChild, renderChild);
|
|
201
|
-
this.updateTree(targetChild, renderChild);
|
|
202
|
-
}
|
|
203
|
-
} else if (renderChild) {
|
|
204
|
-
targetNode?.appendChild(renderChild);
|
|
205
|
-
} else if (targetChild) {
|
|
206
|
-
targetNode?.removeChild(targetChild);
|
|
246
|
+
throw new Error("Illegal state, both children should be elements");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// The existing and rendered child either aren't both editable or are different kinds of editables
|
|
250
|
+
if (!areEqualEditables(renderChild, targetChild)) {
|
|
251
|
+
targetChild.replaceWith(renderChild);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!isEditableElement(renderChild) || !isEditableElement(targetChild)) {
|
|
256
|
+
throw new Error("Illegal state, both children should be editable");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Both elements are editable but the existing element hasn't hydrated
|
|
260
|
+
if (!hasEditable(targetChild)) {
|
|
261
|
+
targetChild.replaceWith(renderChild);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// The existing and rendered child are the same kind of non-text editable.
|
|
266
|
+
if (!isEditableText(targetChild)) {
|
|
267
|
+
this.updateEditable(renderChild, targetChild);
|
|
268
|
+
continue;
|
|
207
269
|
}
|
|
208
270
|
|
|
209
|
-
|
|
210
|
-
|
|
271
|
+
// The existing element is currently focused or is already the same as the rendered element
|
|
272
|
+
if (
|
|
273
|
+
(hasEditableText(targetChild) && targetChild.editable.focused) ||
|
|
274
|
+
targetChild?.isEqualNode(renderChild)
|
|
275
|
+
) {
|
|
276
|
+
this.updateEditable(renderChild, targetChild);
|
|
277
|
+
} else {
|
|
278
|
+
targetChild.replaceWith(renderChild);
|
|
279
|
+
}
|
|
211
280
|
}
|
|
212
281
|
}
|
|
213
282
|
|
|
@@ -259,10 +328,16 @@ export default class EditableComponent extends Editable {
|
|
|
259
328
|
for (let i = 0; i < this.listeners.length; i++) {
|
|
260
329
|
const listener = this.listeners[i];
|
|
261
330
|
if (listener.editable.element === targetChild) {
|
|
262
|
-
targetChild.editable.pushValue(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
331
|
+
targetChild.editable.pushValue(
|
|
332
|
+
this.value,
|
|
333
|
+
this.specialProps,
|
|
334
|
+
listener,
|
|
335
|
+
{
|
|
336
|
+
...this.contexts,
|
|
337
|
+
__base_context: this.contextBase ?? {},
|
|
338
|
+
},
|
|
339
|
+
renderChild,
|
|
340
|
+
);
|
|
266
341
|
}
|
|
267
342
|
}
|
|
268
343
|
}
|
|
@@ -288,23 +363,6 @@ export default class EditableComponent extends Editable {
|
|
|
288
363
|
);
|
|
289
364
|
}
|
|
290
365
|
|
|
291
|
-
setupListeners(): void {
|
|
292
|
-
super.setupListeners();
|
|
293
|
-
const key = this.element.dataset.component;
|
|
294
|
-
if (!key) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const component = this.getComponents()?.[key];
|
|
299
|
-
if (!component) {
|
|
300
|
-
document.addEventListener(
|
|
301
|
-
`editable-regions:registered-${key}`,
|
|
302
|
-
() => this.update(),
|
|
303
|
-
{ once: true },
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
366
|
mount(): void {
|
|
309
367
|
if (!this.controlsElement) {
|
|
310
368
|
let editPath: string | undefined;
|
package/nodes/editable-source.ts
CHANGED
|
@@ -45,9 +45,15 @@ export default class EditableSource extends EditableText {
|
|
|
45
45
|
|
|
46
46
|
this.file = CloudCannon.file(this.element.dataset.path);
|
|
47
47
|
this.file.addEventListener("change", () => {
|
|
48
|
-
this.file
|
|
48
|
+
this.file
|
|
49
|
+
?.get()
|
|
50
|
+
.then((source) =>
|
|
51
|
+
this.pushValue(source, {}, { path: "", editable: this }),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
this.file.get().then((source) => {
|
|
55
|
+
this.pushValue(source, {}, { path: "", editable: this });
|
|
49
56
|
});
|
|
50
|
-
this.file.get().then(this.pushValue.bind(this));
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
validateConfiguration(): boolean {
|