@simmalugnt-se/payload-visual-editing 0.3.2 → 0.3.3
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
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.3
|
|
4
|
+
|
|
5
|
+
- Focusing a field in Admin outlines a copy the preview shows, when the page renders a row more
|
|
6
|
+
than once (a desktop and a mobile menu): rendered copies win over hidden ones, and a copy with the
|
|
7
|
+
field over one with only its row. The outline used to go to the first copy in the page, which a
|
|
8
|
+
narrow preview might hide.
|
|
9
|
+
|
|
3
10
|
## 0.3.2
|
|
4
11
|
|
|
5
12
|
- npm links to simmalugnt.se instead of the private source repository.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { ATTR_BLOCK, ATTR_BLOCK_NAME, ATTR_BLOCK_TYPE, ATTR_FIELD, ATTR_GLOBAL, ATTR_RICHTEXT, inScope, isFocusMessage, isLabelsMessage, MESSAGE_TYPE, nodeSnippet, } from "../protocol.js";
|
|
4
4
|
import { HIGHLIGHT_COLOR, HIGHLIGHT_TINT } from "../theme.js";
|
|
5
|
+
import { preferredCandidate } from "./preferred.js";
|
|
5
6
|
const SELECTOR = `[${ATTR_FIELD}], [${ATTR_BLOCK}]`;
|
|
6
7
|
/**
|
|
7
8
|
* Render only in draft mode. Inside the Live Preview window it outlines editable blocks
|
|
@@ -220,21 +221,31 @@ function hitForBlock(blockId, field, labels) {
|
|
|
220
221
|
if (!blockId) {
|
|
221
222
|
// A top-level field: marked outside any block of this document.
|
|
222
223
|
const element = field
|
|
223
|
-
? Array.from(document.querySelectorAll(`[${ATTR_FIELD}="${CSS.escape(field)}"]`))
|
|
224
|
+
? preferredCandidate(Array.from(document.querySelectorAll(`[${ATTR_FIELD}="${CSS.escape(field)}"]`))
|
|
225
|
+
.filter((candidate) => ownerOf(candidate, labels)?.block === null)
|
|
226
|
+
.map((candidate) => ({
|
|
227
|
+
item: candidate,
|
|
228
|
+
rendered: isRendered(candidate),
|
|
229
|
+
isField: true,
|
|
230
|
+
})))
|
|
224
231
|
: undefined;
|
|
225
232
|
return element && field ? { element, field, label: labelFor(null, field, labels) } : null;
|
|
226
233
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
// The same row may be rendered more than once, e.g. in a desktop and a mobile menu.
|
|
235
|
+
const blocks = Array.from(document.querySelectorAll(`[${ATTR_BLOCK}="${CSS.escape(blockId)}"]`)).filter((block) => ownerOf(block, labels));
|
|
236
|
+
return (preferredCandidate(blocks.map((block) => {
|
|
237
|
+
const fieldElement = field
|
|
238
|
+
? Array.from(block.querySelectorAll(`[${ATTR_FIELD}="${CSS.escape(field)}"]`)).find((element) => element.closest(`[${ATTR_BLOCK}]`) === block)
|
|
239
|
+
: undefined;
|
|
240
|
+
const hit = field && fieldElement
|
|
241
|
+
? { element: fieldElement, blockId, field, label: labelFor(block, field, labels) }
|
|
242
|
+
: { element: block, blockId, label: labelFor(block, undefined, labels) };
|
|
243
|
+
return { item: hit, rendered: isRendered(hit.element), isField: hit.field !== undefined };
|
|
244
|
+
})) ?? null);
|
|
245
|
+
}
|
|
246
|
+
/** Laid out on the page: an element inside `display: none` has no boxes. */
|
|
247
|
+
function isRendered(element) {
|
|
248
|
+
return element.getClientRects().length > 0;
|
|
238
249
|
}
|
|
239
250
|
/**
|
|
240
251
|
* Same order as Admin's row header: the editor's block name, then the block's label, then its slug.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One place a row or field is rendered. Pure, so the choice can be tested without a DOM. */
|
|
2
|
+
export type Candidate<T> = {
|
|
3
|
+
item: T;
|
|
4
|
+
/** Laid out on the page; false inside `display: none`, e.g. a desktop menu on a phone. */
|
|
5
|
+
rendered: boolean;
|
|
6
|
+
/** The field itself, not only its row. */
|
|
7
|
+
isField: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Pages render some rows more than once (a desktop and a mobile menu, a list and its images).
|
|
11
|
+
* Prefer a rendered field, then a rendered row, then a hidden field; on a tie the first in the DOM.
|
|
12
|
+
*/
|
|
13
|
+
export declare function preferredCandidate<T>(candidates: Candidate<T>[]): T | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pages render some rows more than once (a desktop and a mobile menu, a list and its images).
|
|
3
|
+
* Prefer a rendered field, then a rendered row, then a hidden field; on a tie the first in the DOM.
|
|
4
|
+
*/
|
|
5
|
+
export function preferredCandidate(candidates) {
|
|
6
|
+
let best;
|
|
7
|
+
for (const candidate of candidates) {
|
|
8
|
+
if (!best || rank(candidate) > rank(best)) {
|
|
9
|
+
best = candidate;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return best?.item;
|
|
13
|
+
}
|
|
14
|
+
function rank(candidate) {
|
|
15
|
+
return (candidate.rendered ? 2 : 0) + (candidate.isField ? 1 : 0);
|
|
16
|
+
}
|