@cloudcannon/editable-regions 0.0.2 → 0.0.4

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.
@@ -31,6 +31,9 @@ export default class EditableArray extends Editable {
31
31
  | null
32
32
  | undefined = undefined;
33
33
 
34
+ private updatePromise: Promise<void> | undefined;
35
+ private needsReupdate = false;
36
+
34
37
  validateConfiguration(): boolean {
35
38
  const prop = this.element.dataset.prop;
36
39
  if (typeof prop !== "string") {
@@ -65,7 +68,22 @@ export default class EditableArray extends Editable {
65
68
  return value;
66
69
  }
67
70
 
68
- async update(): Promise<void> {
71
+ update(): Promise<void> {
72
+ if (this.updatePromise) {
73
+ this.needsReupdate = true;
74
+ return this.updatePromise;
75
+ }
76
+ this.updatePromise = this._update().then(() => {
77
+ this.updatePromise = undefined;
78
+ if (this.needsReupdate) {
79
+ this.needsReupdate = false;
80
+ return this.update();
81
+ }
82
+ });
83
+ return this.updatePromise;
84
+ }
85
+
86
+ private async _update(): Promise<void> {
69
87
  let value: unknown[] | CloudCannonJavaScriptV1APIFile[];
70
88
  if (CloudCannon.isAPICollection(this.value)) {
71
89
  value = await this.value.items();
@@ -119,7 +137,11 @@ export default class EditableArray extends Editable {
119
137
  }
120
138
  child.dataset.prop = `${i}`;
121
139
  child.dataset.length = `${children.length}`;
122
- child.editable?.pushValue(value[i]);
140
+ child.editable?.pushValue(
141
+ value,
142
+ { path: `${i}`, editable: child.editable },
143
+ { __base_context: this.contextBase ?? {} },
144
+ );
123
145
  }
124
146
  return;
125
147
  }
@@ -155,7 +177,12 @@ export default class EditableArray extends Editable {
155
177
  if (equal && children.length === dataKeys?.length) {
156
178
  children.forEach((child, index) => {
157
179
  child.dataset.prop = `${index}`;
158
- child.editable.pushValue(value[index]);
180
+ child.dataset.length = `${children.length}`;
181
+ child.editable.pushValue(
182
+ value,
183
+ { path: `${index}`, editable: child.editable },
184
+ { __base_context: this.contextBase ?? {} },
185
+ );
159
186
  });
160
187
  return;
161
188
  }
@@ -209,7 +236,11 @@ export default class EditableArray extends Editable {
209
236
  }
210
237
 
211
238
  matchingChild.editable.parent = this;
212
- matchingChild.editable.pushValue(value[i]);
239
+ matchingChild.editable.pushValue(
240
+ value,
241
+ { path: `${i}`, editable: matchingChild.editable },
242
+ { __base_context: this.contextBase ?? {} },
243
+ );
213
244
  });
214
245
 
215
246
  children.forEach((child, i) => {
@@ -11,35 +11,30 @@ import "../components/ui/editable-region-error-card.js";
11
11
  import "../components/ui/editable-component-controls.js";
12
12
  import type EditableComponentControls from "../components/ui/editable-component-controls.js";
13
13
  import {
14
- CloudCannon,
15
14
  getEditableComponentRenderers,
15
+ realizeAPIValue,
16
16
  } from "../helpers/cloudcannon.js";
17
17
 
18
- const realizeAPIValue = async (value: unknown): Promise<unknown> => {
19
- if (CloudCannon.isAPICollection(value)) {
20
- const items = await value.items();
21
- return Promise.all(items.map(realizeAPIValue));
22
- }
23
- if (CloudCannon.isAPIFile(value)) {
24
- return value.data.get();
25
- }
26
- if (CloudCannon.isAPIDataset(value)) {
27
- const items = await value.items();
28
- if (Array.isArray(items)) {
29
- return Promise.all(items.map(realizeAPIValue));
30
- }
31
- return items.data.get();
32
- }
33
- return value;
34
- };
35
-
36
18
  export default class EditableComponent extends Editable {
37
19
  protected controlsElement?: EditableComponentControls;
38
20
 
21
+ private updatePromise: Promise<void> | undefined;
22
+ private needsReupdate = false;
23
+
39
24
  getComponents() {
40
25
  return getEditableComponentRenderers();
41
26
  }
42
27
 
28
+ shouldMount(): boolean {
29
+ if (super.shouldMount()) {
30
+ return true;
31
+ }
32
+
33
+ return !Object.keys(this.element.dataset).some((key) =>
34
+ key.startsWith("prop"),
35
+ );
36
+ }
37
+
43
38
  validateConfiguration(): boolean {
44
39
  const key = this.element.dataset.component;
45
40
  if (!key) {
@@ -57,7 +52,22 @@ export default class EditableComponent extends Editable {
57
52
  return true;
58
53
  }
59
54
 
60
- async update(): Promise<void> {
55
+ update(): Promise<void> {
56
+ if (this.updatePromise) {
57
+ this.needsReupdate = true;
58
+ return this.updatePromise;
59
+ }
60
+ this.updatePromise = this._update().then(() => {
61
+ this.updatePromise = undefined;
62
+ if (this.needsReupdate) {
63
+ this.needsReupdate = false;
64
+ return this.update();
65
+ }
66
+ });
67
+ return this.updatePromise;
68
+ }
69
+
70
+ async _update(): Promise<void> {
61
71
  this.element.classList.remove("errored");
62
72
 
63
73
  const key = this.element.dataset.component;
@@ -168,24 +178,17 @@ export default class EditableComponent extends Editable {
168
178
  const listener = this.listeners[i];
169
179
  if (listener.editable.element === targetChild) {
170
180
  listener.editable.element = renderChild;
171
- renderChild.editable.pushValue(this.value, listener);
181
+ renderChild.editable.pushValue(this.value, listener, {
182
+ ...this.contexts,
183
+ __base_context: this.contextBase ?? {},
184
+ });
172
185
  }
173
186
  }
174
187
  } else if (hasEditable(targetChild)) {
175
- for (let i = 0; i < this.listeners.length; i++) {
176
- const listener = this.listeners[i];
177
- if (listener.editable.element === targetChild) {
178
- targetChild.editable.pushValue(this.value, listener);
179
- }
180
- }
188
+ this.updateEditable(renderChild, targetChild);
181
189
  }
182
190
  } else if (hasEditable(targetChild)) {
183
- for (let i = 0; i < this.listeners.length; i++) {
184
- const listener = this.listeners[i];
185
- if (listener.editable.element === targetChild) {
186
- targetChild.editable.pushValue(this.value, listener);
187
- }
188
- }
191
+ this.updateEditable(renderChild, targetChild);
189
192
  }
190
193
  } else if (renderChild && targetChild) {
191
194
  if (
@@ -208,11 +211,66 @@ export default class EditableComponent extends Editable {
208
211
  }
209
212
  }
210
213
 
211
- dispatchEdit(source?: string) {
214
+ updateEditable(
215
+ renderChild: HTMLElement,
216
+ targetChild: HTMLElement & { editable: Editable },
217
+ ) {
218
+ for (const attribute of renderChild.attributes) {
219
+ if (attribute.name !== "class") {
220
+ targetChild.setAttribute(attribute.name, attribute.value);
221
+ }
222
+ }
223
+ for (const attribute of targetChild.attributes) {
224
+ if (
225
+ !renderChild.hasAttribute(attribute.name) &&
226
+ attribute.name !== "class" &&
227
+ attribute.name !== "contenteditable"
228
+ ) {
229
+ targetChild.removeAttribute(attribute.name);
230
+ }
231
+ }
232
+
233
+ for (const className of renderChild.classList) {
234
+ targetChild.classList.add(className);
235
+ }
236
+
237
+ for (const className of targetChild.classList) {
238
+ if (
239
+ !renderChild.classList.contains(className) &&
240
+ !className.includes("ProseMirror")
241
+ ) {
242
+ targetChild.classList.remove(className);
243
+ }
244
+ }
245
+
246
+ for (let i = 0; i < this.listeners.length; i++) {
247
+ const listener = this.listeners[i];
248
+ if (listener.editable.element === targetChild) {
249
+ targetChild.editable.pushValue(this.value, listener, {
250
+ ...this.contexts,
251
+ __base_context: this.contextBase ?? {},
252
+ });
253
+ }
254
+ }
255
+ }
256
+
257
+ dispatchEdit(source?: string, originalEvent?: MouseEvent) {
258
+ const rect = this.element.getBoundingClientRect();
212
259
  this.element.dispatchEvent(
213
260
  new CustomEvent("cloudcannon-api", {
214
261
  bubbles: true,
215
- detail: { action: "edit", source },
262
+ detail: {
263
+ action: "edit",
264
+ source,
265
+ position: {
266
+ x: originalEvent?.clientX ?? 0,
267
+ y: originalEvent?.clientY ?? 0,
268
+ left: rect.left,
269
+ width: rect.width,
270
+ top: rect.top,
271
+ height: rect.height,
272
+ },
273
+ },
216
274
  }),
217
275
  );
218
276
  }
@@ -139,10 +139,4 @@ export default class EditableSnippet extends EditableComponent {
139
139
  validateConfiguration(): boolean {
140
140
  return true;
141
141
  }
142
-
143
- resolveSource(source?: string): string | undefined {
144
- return this.parent
145
- ? `${this.parent.resolveSource()}.@snippet[${this.element.getAttribute("data-cms-snippet-id")}].${source}`
146
- : `@snippet[${this.element.getAttribute("data-cms-snippet-id")}].${source}`;
147
- }
148
142
  }
@@ -117,13 +117,7 @@ export default class EditableText extends Editable {
117
117
  return this.editor;
118
118
  }
119
119
 
120
- const source = this.resolveSource();
121
-
122
- if (!source) {
123
- throw new Error("Source not found");
124
- }
125
-
126
- const inputConfig = source.endsWith("@content")
120
+ const inputConfig = this.contextBase?.isContent
127
121
  ? { type: "markdown" }
128
122
  : await this.dispatchGetInputConfig(this.element.dataset.prop);
129
123
 
@@ -132,7 +126,7 @@ export default class EditableText extends Editable {
132
126
  this.onChange.bind(this),
133
127
  {
134
128
  elementType: this.element.dataset.type,
135
- editableType: source.endsWith("@content") ? "content" : undefined,
129
+ editableType: this.contextBase?.isContent ? "content" : undefined,
136
130
  inputConfig,
137
131
  },
138
132
  );