@simmalugnt-se/payload-visual-editing 0.1.2 → 0.2.1
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 +13 -0
- package/README.md +20 -0
- package/dist/admin/VisualEditingAdminBridge.js +15 -4
- package/dist/frontend/VisualEditingPreview.js +11 -2
- package/dist/protocol.d.ts +15 -0
- package/dist/protocol.js +16 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- A preview click right after the page loads no longer lands on the wrong tab: Payload restores the
|
|
6
|
+
editor's last tab once preferences load, and now leaves it alone because the bridge always clicks
|
|
7
|
+
the tab it needs.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
- A click in the preview dispatches `visual-editing:select` on Admin's `window` with the form path,
|
|
12
|
+
so other Admin components can follow the selection, and with `path: null` when the editor clears
|
|
13
|
+
it in the preview (empty space, Escape).
|
|
14
|
+
- Clicks inside `data-visual-editing-keep-selection` keep the preview outline.
|
|
15
|
+
|
|
3
16
|
## 0.1.2
|
|
4
17
|
|
|
5
18
|
- A click in the preview no longer blocks the page's own click handlers: accordions, tabs and
|
package/README.md
CHANGED
|
@@ -78,3 +78,23 @@ Requires Payload `>=3.85.2 <4` and React 19.
|
|
|
78
78
|
The outline shows the same names as Admin: the editor's block name, otherwise the block's
|
|
79
79
|
`labels.singular`, then the field's `label`, e.g. "CTA › Button label". Array rows get their row
|
|
80
80
|
header, e.g. "Item 02 › Title". Admin sends the labels to the preview, so nothing needs configuring.
|
|
81
|
+
|
|
82
|
+
## Following the selection in other Admin components
|
|
83
|
+
|
|
84
|
+
A click in the preview dispatches `visual-editing:select` on Admin's `window`, with the form path
|
|
85
|
+
in `detail.path` (`layout.2` for a block, `layout.2.headline` for a field), and with `path: null`
|
|
86
|
+
when the editor clears the selection in the preview (empty space, Escape). Other components can
|
|
87
|
+
follow the editor's selection without depending on this package;
|
|
88
|
+
`@simmalugnt-se/payload-editor-assistant` uses it so "this" in a message means the clicked block.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
window.addEventListener("visual-editing:select", (event) => {
|
|
92
|
+
const { path } = (event as CustomEvent<{ path: string | null }>).detail;
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Clicks in Admin clear the preview outline. Put `data-visual-editing-keep-selection` on a panel
|
|
97
|
+
whose clicks should keep it, such as a chat next to the form.
|
|
98
|
+
|
|
99
|
+
The event name, its `detail` and the attribute are public API: renaming or removing them is a
|
|
100
|
+
breaking change and needs a major version.
|
|
@@ -4,7 +4,7 @@ import { useConfig, useDocumentForm, useDocumentInfo, useLivePreviewContext, use
|
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
5
|
import "../styles/admin.css";
|
|
6
6
|
import { collectBlockLabels, collectRowLabels } from "../labels.js";
|
|
7
|
-
import { fieldDomId, focusTargets, isReadyMessage, isSelectMessage, MESSAGE_TYPE, matchNodeIndex, pathFromDomId, pathFromRowDomId, resolveBlockTarget, rowDomId, } from "../protocol.js";
|
|
7
|
+
import { ATTR_KEEP_SELECTION, fieldDomId, focusTargets, isClearMessage, isReadyMessage, isSelectMessage, MESSAGE_TYPE, matchNodeIndex, pathFromDomId, pathFromRowDomId, resolveBlockTarget, rowDomId, SELECT_EVENT, } from "../protocol.js";
|
|
8
8
|
import { planReveal } from "../reveal-plan.js";
|
|
9
9
|
import { HIGHLIGHT_TINT } from "../theme.js";
|
|
10
10
|
const HIGHLIGHT_CLASS = "pve-highlight";
|
|
@@ -50,6 +50,11 @@ export function VisualEditingAdminBridge() {
|
|
|
50
50
|
sendLabels(event.source, event.origin);
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
+
if (isClearMessage(event.data)) {
|
|
54
|
+
run += 1;
|
|
55
|
+
window.dispatchEvent(new CustomEvent(SELECT_EVENT, { detail: { path: null } }));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
53
58
|
if (!isSelectMessage(event.data)) {
|
|
54
59
|
return;
|
|
55
60
|
}
|
|
@@ -85,6 +90,9 @@ export function VisualEditingAdminBridge() {
|
|
|
85
90
|
const fieldPath = message.field ? `${target.rowPath}.${message.field}` : undefined;
|
|
86
91
|
const fieldPlan = fieldPath ? planReveal(rootFields, fieldPath, lookup) : null;
|
|
87
92
|
const plan = fieldPlan ?? planReveal(rootFields, target.rowPath, lookup) ?? [];
|
|
93
|
+
window.dispatchEvent(new CustomEvent(SELECT_EVENT, {
|
|
94
|
+
detail: { path: fieldPlan && fieldPath ? fieldPath : target.rowPath },
|
|
95
|
+
}));
|
|
88
96
|
const root = formRoot(anchorRef.current);
|
|
89
97
|
const scope = await openSteps(plan, root, () => id === run);
|
|
90
98
|
if (!scope || id !== run) {
|
|
@@ -117,6 +125,9 @@ export function VisualEditingAdminBridge() {
|
|
|
117
125
|
// Focus moving into the preview iframe is the preview's own click; it already selected.
|
|
118
126
|
return;
|
|
119
127
|
}
|
|
128
|
+
if (event.target.closest(`[${ATTR_KEEP_SELECTION}]`)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
120
131
|
const root = formRoot(anchorRef.current);
|
|
121
132
|
const path = root.contains(event.target) ? pathOf(event.target) : undefined;
|
|
122
133
|
const fields = latest.current.form.getFields();
|
|
@@ -221,9 +232,9 @@ async function openSteps(steps, root, current) {
|
|
|
221
232
|
if (!tabs || !button) {
|
|
222
233
|
return null;
|
|
223
234
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
// Click even the active tab: Payload restores the editor's last tab from preferences once they
|
|
236
|
+
// load, unless a tab was clicked first. A click right after the page loads would lose to it.
|
|
237
|
+
button.click();
|
|
227
238
|
const content = await waitFor(() => button.classList.contains("tabs-field__tab-button--active")
|
|
228
239
|
? tabs.querySelector(":scope > .tabs-field__content-wrap")
|
|
229
240
|
: null);
|
|
@@ -75,11 +75,20 @@ export function VisualEditingPreview({ adminOrigin }) {
|
|
|
75
75
|
redraw();
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
|
+
/** The editor dropped the selection here; Admin tells components following it. */
|
|
79
|
+
const dropSelection = () => {
|
|
80
|
+
if (!current) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
clearSelection();
|
|
84
|
+
const message = { type: MESSAGE_TYPE, action: "clear" };
|
|
85
|
+
admin.postMessage(message, targetOrigin);
|
|
86
|
+
};
|
|
78
87
|
const onClick = (event) => {
|
|
79
88
|
const hit = hitFromTarget(event.target, labels);
|
|
80
89
|
if (!hit) {
|
|
81
90
|
// Outside any editable element the page behaves normally; only the selection is dropped.
|
|
82
|
-
|
|
91
|
+
dropSelection();
|
|
83
92
|
return;
|
|
84
93
|
}
|
|
85
94
|
// A click selects, and must not leave the page: links and submits are cancelled. Anything
|
|
@@ -134,7 +143,7 @@ export function VisualEditingPreview({ adminOrigin }) {
|
|
|
134
143
|
};
|
|
135
144
|
const onKeyDown = (event) => {
|
|
136
145
|
if (event.key === "Escape") {
|
|
137
|
-
|
|
146
|
+
dropSelection();
|
|
138
147
|
}
|
|
139
148
|
};
|
|
140
149
|
document.addEventListener("pointermove", onPointerMove, { passive: true });
|
package/dist/protocol.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ export declare const ATTR_BLOCK_NAME = "data-payload-block-name";
|
|
|
9
9
|
export declare const ATTR_FIELD = "data-payload-field";
|
|
10
10
|
/** On the rich text container whose direct children are the top-level editor nodes. */
|
|
11
11
|
export declare const ATTR_RICHTEXT = "data-payload-richtext";
|
|
12
|
+
/**
|
|
13
|
+
* Dispatched on Admin's `window` when a preview click selects something, with the form path as
|
|
14
|
+
* `detail.path` (`layout.2` for a block, `layout.2.headline` for a field), and with `path: null`
|
|
15
|
+
* when the editor clears the selection in the preview. Other Admin components, such as an
|
|
16
|
+
* assistant, can follow the editor's selection without depending on this package.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SELECT_EVENT = "visual-editing:select";
|
|
19
|
+
/** Clicks and focus inside an element with this attribute keep the preview outline. */
|
|
20
|
+
export declare const ATTR_KEEP_SELECTION = "data-visual-editing-keep-selection";
|
|
12
21
|
export type SelectMessage = {
|
|
13
22
|
type: typeof MESSAGE_TYPE;
|
|
14
23
|
action: "select";
|
|
@@ -21,6 +30,12 @@ export type SelectMessage = {
|
|
|
21
30
|
text?: string;
|
|
22
31
|
};
|
|
23
32
|
};
|
|
33
|
+
/** Preview → Admin: the editor dropped the selection in the preview (empty space, Escape). */
|
|
34
|
+
export type ClearMessage = {
|
|
35
|
+
type: typeof MESSAGE_TYPE;
|
|
36
|
+
action: "clear";
|
|
37
|
+
};
|
|
38
|
+
export declare function isClearMessage(value: unknown): value is ClearMessage;
|
|
24
39
|
export declare function isSelectMessage(value: unknown): value is SelectMessage;
|
|
25
40
|
/**
|
|
26
41
|
* Pick the editor node matching the clicked preview node. Text wins over index because
|
package/dist/protocol.js
CHANGED
|
@@ -8,7 +8,23 @@ export const ATTR_BLOCK_NAME = "data-payload-block-name";
|
|
|
8
8
|
export const ATTR_FIELD = "data-payload-field";
|
|
9
9
|
/** On the rich text container whose direct children are the top-level editor nodes. */
|
|
10
10
|
export const ATTR_RICHTEXT = "data-payload-richtext";
|
|
11
|
+
/**
|
|
12
|
+
* Dispatched on Admin's `window` when a preview click selects something, with the form path as
|
|
13
|
+
* `detail.path` (`layout.2` for a block, `layout.2.headline` for a field), and with `path: null`
|
|
14
|
+
* when the editor clears the selection in the preview. Other Admin components, such as an
|
|
15
|
+
* assistant, can follow the editor's selection without depending on this package.
|
|
16
|
+
*/
|
|
17
|
+
export const SELECT_EVENT = "visual-editing:select";
|
|
18
|
+
/** Clicks and focus inside an element with this attribute keep the preview outline. */
|
|
19
|
+
export const ATTR_KEEP_SELECTION = "data-visual-editing-keep-selection";
|
|
11
20
|
const MAX_NODE_TEXT = 200;
|
|
21
|
+
export function isClearMessage(value) {
|
|
22
|
+
if (!value || typeof value !== "object") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const message = value;
|
|
26
|
+
return message.type === MESSAGE_TYPE && message.action === "clear";
|
|
27
|
+
}
|
|
12
28
|
export function isSelectMessage(value) {
|
|
13
29
|
if (!value || typeof value !== "object") {
|
|
14
30
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simmalugnt-se/payload-visual-editing",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Click a block in Payload Live Preview to open its fields in the edit form",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"react-dom": "^19.0.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@payloadcms/ui": "3.
|
|
54
|
+
"@payloadcms/ui": "3.90.2",
|
|
55
55
|
"@types/node": "^22",
|
|
56
56
|
"@types/react": "^19.2.18",
|
|
57
57
|
"@types/react-dom": "^19.2.4",
|
|
58
|
-
"payload": "3.
|
|
58
|
+
"payload": "3.90.2",
|
|
59
59
|
"react": "19.2.8",
|
|
60
60
|
"react-dom": "19.2.8",
|
|
61
61
|
"tsx": "^4.21.0",
|