@simmalugnt-se/payload-visual-editing 0.3.0 → 0.3.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/CHANGELOG.md +10 -0
- package/dist/admin/VisualEditingAdminBridge.js +28 -4
- package/dist/reveal-plan.d.ts +10 -2
- package/dist/reveal-plan.js +41 -21
- package/package.json +2 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
- npm links to simmalugnt.se instead of the private source repository.
|
|
6
|
+
|
|
7
|
+
## 0.3.1
|
|
8
|
+
|
|
9
|
+
- Preview clicks open collapsed Payload `collapsible` fields around the target, including nested
|
|
10
|
+
collapsibles and those inside tabs, groups and rows. The bridge finds each by the id Payload
|
|
11
|
+
gives it, so a collapsible hidden by a condition does not change which one opens.
|
|
12
|
+
|
|
3
13
|
## 0.3.0
|
|
4
14
|
|
|
5
15
|
- Globals: `visualEditingPlugin({ globals: ["header", "footer"] })` supports click-to-edit in a
|
|
@@ -230,8 +230,8 @@ function formRoot(anchor) {
|
|
|
230
230
|
return view?.querySelector(".collection-edit__form") ?? document;
|
|
231
231
|
}
|
|
232
232
|
/**
|
|
233
|
-
* Open the rows and
|
|
234
|
-
* Returns the innermost scope
|
|
233
|
+
* Open the rows, tabs and collapsibles the plan needs, one level at a time.
|
|
234
|
+
* Returns the innermost scope, where the target field renders.
|
|
235
235
|
*/
|
|
236
236
|
async function openSteps(steps, root, current) {
|
|
237
237
|
let scope = root;
|
|
@@ -247,6 +247,26 @@ async function openSteps(steps, root, current) {
|
|
|
247
247
|
scope = row;
|
|
248
248
|
continue;
|
|
249
249
|
}
|
|
250
|
+
if (step.kind === "collapsible") {
|
|
251
|
+
const collapsibleScope = scope;
|
|
252
|
+
const field = await waitForRendered(() => collapsibleScope.querySelector(`[id="${collapsibleDomId(step.path)}"]`), collapsibleScope);
|
|
253
|
+
const element = field?.querySelector(":scope > .collapsible");
|
|
254
|
+
const button = element?.querySelector(":scope > .collapsible__toggle-wrap > .collapsible__toggle");
|
|
255
|
+
if (!element || !button) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
if (element.classList.contains("collapsible--collapsed")) {
|
|
259
|
+
button.click();
|
|
260
|
+
}
|
|
261
|
+
const content = await waitFor(() => element.classList.contains("collapsible--collapsed")
|
|
262
|
+
? null
|
|
263
|
+
: element.querySelector(".collapsible__content"));
|
|
264
|
+
if (!content) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
scope = content;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
250
270
|
const tabsScope = scope;
|
|
251
271
|
const tabs = await waitForRendered(() => tabsFieldsIn(tabsScope)[step.ordinal] ?? null, tabsScope);
|
|
252
272
|
const button = tabs?.querySelectorAll(":scope > .tabs-field__tabs-wrap > .tabs-field__tabs > .tabs-field__tab-button")[step.index];
|
|
@@ -281,13 +301,17 @@ async function waitForRendered(find, scope, timeoutMs = WAIT_MS) {
|
|
|
281
301
|
}
|
|
282
302
|
return waitFor(find, timeoutMs);
|
|
283
303
|
}
|
|
284
|
-
/** Tabs fields at this level of the scope, in DOM order; nested
|
|
304
|
+
/** Tabs fields at this level of the scope, in DOM order; nested containers are other scopes. */
|
|
285
305
|
function tabsFieldsIn(scope) {
|
|
286
306
|
return Array.from(scope.querySelectorAll(".tabs-field")).filter((element) => {
|
|
287
|
-
const outer = element.parentElement?.closest(".tabs-field, .blocks-field, .array-field");
|
|
307
|
+
const outer = element.parentElement?.closest(".tabs-field, .collapsible-field, .blocks-field, .array-field");
|
|
288
308
|
return !outer || !scope.contains(outer);
|
|
289
309
|
});
|
|
290
310
|
}
|
|
311
|
+
/** Payload's id for a collapsible field: `field-collapsible-` and its path with `__` for dots. */
|
|
312
|
+
function collapsibleDomId(path) {
|
|
313
|
+
return `field-collapsible-${path.replace(/\./g, "__")}`;
|
|
314
|
+
}
|
|
291
315
|
function waitFor(find, timeoutMs = WAIT_MS) {
|
|
292
316
|
const deadline = performance.now() + timeoutMs;
|
|
293
317
|
return new Promise((resolve) => {
|
package/dist/reveal-plan.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Work out which tabs must be opened to show a form path.
|
|
3
|
-
* so a field in another tab has no DOM element until its tab button is clicked.
|
|
2
|
+
* Work out which tabs and collapsibles must be opened to show a form path.
|
|
4
3
|
* Pure: takes client field config, returns steps for the Admin bridge to perform.
|
|
5
4
|
*/
|
|
6
5
|
export type FieldLike = {
|
|
@@ -33,6 +32,15 @@ export type RevealStep =
|
|
|
33
32
|
ordinal: number;
|
|
34
33
|
index: number;
|
|
35
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Open the collapsible field Payload renders at this path, then enter its content. The path is
|
|
37
|
+
* Payload's own for an unnamed field (`meta._index-0-1`) and gives its DOM id, so a collapsible
|
|
38
|
+
* hidden by a condition does not shift which one is opened.
|
|
39
|
+
*/
|
|
40
|
+
| {
|
|
41
|
+
kind: "collapsible";
|
|
42
|
+
path: string;
|
|
43
|
+
}
|
|
36
44
|
/** Enter a block or array row. The row element becomes the scope. */
|
|
37
45
|
| {
|
|
38
46
|
kind: "row";
|
package/dist/reveal-plan.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Work out which tabs must be opened to show a form path.
|
|
3
|
-
* so a field in another tab has no DOM element until its tab button is clicked.
|
|
2
|
+
* Work out which tabs and collapsibles must be opened to show a form path.
|
|
4
3
|
* Pure: takes client field config, returns steps for the Admin bridge to perform.
|
|
5
4
|
*/
|
|
6
5
|
export function planReveal(rootFields, path, lookup) {
|
|
@@ -14,22 +13,22 @@ function walkPath(rootFields, path, lookup) {
|
|
|
14
13
|
const segments = path.split(".");
|
|
15
14
|
const steps = [];
|
|
16
15
|
let fields = rootFields;
|
|
17
|
-
let counter = {
|
|
16
|
+
let counter = { tabs: 0 };
|
|
18
17
|
let walked = [];
|
|
19
18
|
let last = null;
|
|
20
19
|
for (let i = 0; i < segments.length;) {
|
|
21
20
|
const segment = segments[i] ?? "";
|
|
22
|
-
const found = findIn(fields, segment, counter);
|
|
21
|
+
const found = findIn(fields, segment, counter, { dataPath: walked.join("."), indexPath: "" });
|
|
23
22
|
if (!found) {
|
|
24
23
|
return null;
|
|
25
24
|
}
|
|
26
|
-
steps.push(...found.
|
|
25
|
+
steps.push(...found.steps);
|
|
27
26
|
counter = found.counter;
|
|
28
27
|
walked = [...walked, segment];
|
|
29
28
|
i += 1;
|
|
30
29
|
if (found.kind === "namedTab") {
|
|
31
30
|
fields = found.fields;
|
|
32
|
-
counter = {
|
|
31
|
+
counter = { tabs: 0 };
|
|
33
32
|
continue;
|
|
34
33
|
}
|
|
35
34
|
const field = found.field;
|
|
@@ -42,7 +41,7 @@ function walkPath(rootFields, path, lookup) {
|
|
|
42
41
|
steps.push({ kind: "row", parentPath, index: Number(next) });
|
|
43
42
|
walked = [...walked, next];
|
|
44
43
|
i += 1;
|
|
45
|
-
counter = {
|
|
44
|
+
counter = { tabs: 0 };
|
|
46
45
|
if (field.type === "array") {
|
|
47
46
|
fields = field.fields ?? [];
|
|
48
47
|
continue;
|
|
@@ -55,7 +54,7 @@ function walkPath(rootFields, path, lookup) {
|
|
|
55
54
|
continue;
|
|
56
55
|
}
|
|
57
56
|
if (field.fields) {
|
|
58
|
-
// Named group: same DOM scope, keep counting
|
|
57
|
+
// Named group: same DOM scope, keep counting revealable fields.
|
|
59
58
|
fields = field.fields;
|
|
60
59
|
continue;
|
|
61
60
|
}
|
|
@@ -64,51 +63,72 @@ function walkPath(rootFields, path, lookup) {
|
|
|
64
63
|
}
|
|
65
64
|
return { steps, field: last };
|
|
66
65
|
}
|
|
67
|
-
function findIn(fields, name, counter) {
|
|
68
|
-
for (const field of fields) {
|
|
66
|
+
function findIn(fields, name, counter, place) {
|
|
67
|
+
for (const [position, field] of fields.entries()) {
|
|
69
68
|
if (field.type === "tabs") {
|
|
70
|
-
const ordinal = counter.
|
|
71
|
-
counter.
|
|
69
|
+
const ordinal = counter.tabs;
|
|
70
|
+
counter.tabs += 1;
|
|
71
|
+
const tabsPlace = unnamedChild(place, position);
|
|
72
72
|
for (const [index, tab] of (field.tabs ?? []).entries()) {
|
|
73
73
|
const step = { kind: "tab", ordinal, index };
|
|
74
74
|
if (tab.name === name) {
|
|
75
75
|
return {
|
|
76
76
|
kind: "namedTab",
|
|
77
77
|
fields: tab.fields ?? [],
|
|
78
|
-
|
|
79
|
-
counter: {
|
|
78
|
+
steps: [step],
|
|
79
|
+
counter: { tabs: 0 },
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
if (!tab.name) {
|
|
83
|
-
const inner = findIn(tab.fields ?? [], name, {
|
|
83
|
+
const inner = findIn(tab.fields ?? [], name, { tabs: 0 }, unnamedChild(tabsPlace, index));
|
|
84
84
|
if (inner) {
|
|
85
|
-
return { ...inner,
|
|
85
|
+
return { ...inner, steps: [step, ...inner.steps] };
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
continue;
|
|
90
90
|
}
|
|
91
91
|
if (field.name === name) {
|
|
92
|
-
return { kind: "field", field,
|
|
92
|
+
return { kind: "field", field, steps: [], counter };
|
|
93
93
|
}
|
|
94
94
|
if (!field.name && field.fields) {
|
|
95
|
-
// Row, collapsible or unnamed group:
|
|
96
|
-
|
|
95
|
+
// Row, collapsible or unnamed group: no segment in the form path. A collapsible is its own
|
|
96
|
+
// DOM scope, found by the path Payload gives it.
|
|
97
|
+
const own = unnamedChild(place, position);
|
|
98
|
+
if (field.type === "collapsible") {
|
|
99
|
+
const inner = findIn(field.fields, name, { tabs: 0 }, own);
|
|
100
|
+
if (inner) {
|
|
101
|
+
const path = [place.dataPath, `_index-${own.indexPath}`].filter(Boolean).join(".");
|
|
102
|
+
return { ...inner, steps: [{ kind: "collapsible", path }, ...inner.steps] };
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const inner = findIn(field.fields, name, counter, own);
|
|
97
107
|
if (inner) {
|
|
98
108
|
return inner;
|
|
99
109
|
}
|
|
100
110
|
continue;
|
|
101
111
|
}
|
|
102
|
-
counter.
|
|
112
|
+
counter.tabs += countTabs(field);
|
|
103
113
|
}
|
|
104
114
|
return null;
|
|
105
115
|
}
|
|
116
|
+
/** The place of the unnamed field at `position`, whose own fields render there too. */
|
|
117
|
+
function unnamedChild(place, position) {
|
|
118
|
+
return {
|
|
119
|
+
dataPath: place.dataPath,
|
|
120
|
+
indexPath: place.indexPath ? `${place.indexPath}-${position}` : String(position),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
106
123
|
/** Tabs fields rendered inside a field that is skipped, so later ordinals stay aligned with the DOM. */
|
|
107
124
|
function countTabs(field) {
|
|
108
125
|
if (field.type === "tabs") {
|
|
109
126
|
return 1;
|
|
110
127
|
}
|
|
111
|
-
if (field.type === "blocks" ||
|
|
128
|
+
if (field.type === "blocks" ||
|
|
129
|
+
field.type === "array" ||
|
|
130
|
+
field.type === "collapsible" ||
|
|
131
|
+
!field.fields) {
|
|
112
132
|
return 0;
|
|
113
133
|
}
|
|
114
134
|
return field.fields.reduce((sum, child) => sum + countTabs(child), 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simmalugnt-se/payload-visual-editing",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Click a block in Payload Live Preview to open its fields in the edit form",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -8,11 +8,7 @@
|
|
|
8
8
|
],
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"author": "Simmalugnt AB",
|
|
11
|
-
"
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/simmalugnt-se/payload-plugins.git",
|
|
14
|
-
"directory": "packages/payload-visual-editing"
|
|
15
|
-
},
|
|
11
|
+
"homepage": "https://simmalugnt.se",
|
|
16
12
|
"type": "module",
|
|
17
13
|
"engines": {
|
|
18
14
|
"node": ">=22"
|