@preprio/toolkit 0.2.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/README.md +55 -1
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/{chunk-4PJK6IQ4.js → chunk-GIEOTQD4.js} +16 -6
- package/dist/index.cjs +17 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/nextjs/components.cjs +16 -6
- package/dist/nextjs/components.js +1 -1
- package/dist/nextjs/index.cjs +1 -1
- package/dist/nextjs/index.js +1 -1
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/react/components.cjs +16 -6
- package/dist/react/components.js +16 -6
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @preprio/toolkit
|
|
2
2
|
|
|
3
|
+
> **Beta — pre-1.0.** The package is published to `latest` and is safe to use, but the
|
|
4
|
+
> public API can still change between minor versions (`0.2.x` → `0.3.0`). Every change
|
|
5
|
+
> is documented in [Breaking changes](https://github.com/preprio/prepr-toolkit/blob/main/RELEASING.md#breaking-changes). Pin an exact
|
|
6
|
+
> version in production, or use a tilde range (`~0.2.0`) so you only pick up patches.
|
|
7
|
+
|
|
3
8
|
A framework-agnostic TypeScript library that provides preview functionality, visual editing, and A/B testing for [Prepr CMS](https://prepr.io). Ships thin wrappers for Next.js, Nuxt, Astro, and SvelteKit on top of a vanilla core that runs anywhere.
|
|
4
9
|
|
|
5
10
|
Every integration is the same three steps:
|
|
@@ -10,6 +15,8 @@ Every integration is the same three steps:
|
|
|
10
15
|
|
|
11
16
|
Jump to your framework: [Next.js](#nextjs) · [Astro](#astro) · [SvelteKit](#sveltekit) · [Nuxt](#nuxt) · [React without a framework](#react-no-framework) · [anything else](#any-other-framework).
|
|
12
17
|
|
|
18
|
+
Coming from `@preprio/prepr-nextjs`? See the [migration guide](https://github.com/preprio/prepr-toolkit/blob/main/MIGRATION.md).
|
|
19
|
+
|
|
13
20
|
## Quick Start (Next.js)
|
|
14
21
|
|
|
15
22
|
```bash
|
|
@@ -812,6 +819,12 @@ createPreprPreview({ props, options: { locale: 'nl' } });
|
|
|
812
819
|
- **Confirm the middleware matcher**: if `config.matcher` excludes the current path, no Prepr headers were set for it. `getPreprUUID()` returning `null` confirms this.
|
|
813
820
|
- **Check for `prepr_hide_bar=true`** in the URL, which suppresses the bar by design.
|
|
814
821
|
|
|
822
|
+
### Click-to-edit not working on some elements
|
|
823
|
+
|
|
824
|
+
- **Check the token permissions**: "Enable edit mode" must be checked on the token in Prepr.
|
|
825
|
+
- **Check whether the encoded text sits in a hidden element.** The parent of the encoded text node is what becomes editable, and a parent that renders no box (`display: none`, `visibility: hidden`, the `hidden` attribute) is skipped — it cannot be hovered or clicked. Put `data-prepr-edit-target` on the visible ancestor that should be editable instead. See [Choosing the element that becomes editable](#choosing-the-element-that-becomes-editable).
|
|
826
|
+
- **Inspect the tagged elements**: `document.querySelectorAll('[data-prepr-encoded]')` in the console lists everything edit mode found. An element you expected to be in that list points at one of the two cases above.
|
|
827
|
+
|
|
815
828
|
### Headers not working
|
|
816
829
|
|
|
817
830
|
- **Middleware setup**: confirm `middleware.ts` sits in the project root (or `src/`) and its matcher covers the route.
|
|
@@ -883,7 +896,40 @@ The toolbar renders with Preact into a shadow-DOM custom element (`<prepr-toolba
|
|
|
883
896
|
|
|
884
897
|
With edit mode enabled, the toolkit scans for stega-encoded content, strips the invisible Unicode characters after load so they cannot cause layout shifts, highlights editable elements by cursor proximity, and talks to the Prepr editor over a `postMessage` bridge when running inside the live-preview iframe.
|
|
885
898
|
|
|
886
|
-
Stega cleaning is automatic. There is no need to call `vercelStegaSplit` or
|
|
899
|
+
Stega cleaning is automatic. There is no need to call `vercelStegaSplit` or to strip the payload yourself before rendering — the data attributes the toolkit relies on survive the clean, so edit mode still activates instantly.
|
|
900
|
+
|
|
901
|
+
#### Choosing the element that becomes editable
|
|
902
|
+
|
|
903
|
+
The element that gets the outline, the tooltip and the click handler is the **parent of the encoded text node**. Rendering `{title}` inside an `<h2>` makes the `<h2>` editable, which is what you want almost every time.
|
|
904
|
+
|
|
905
|
+
Two rules govern the choice:
|
|
906
|
+
|
|
907
|
+
- A parent that renders no box is skipped. An element hidden with `display: none`, `visibility: hidden`, or the `hidden` attribute cannot be hovered or outlined, so tagging it would produce an element that looks editable to the code and is unreachable to the visitor. Nothing is tagged for it.
|
|
908
|
+
- `data-prepr-edit-target` overrides the parent. The closest ancestor carrying it becomes the editable element instead.
|
|
909
|
+
|
|
910
|
+
That attribute is the supported way to separate _where the payload lives_ from _what the visitor clicks_. It is what makes the hidden-payload pattern work:
|
|
911
|
+
|
|
912
|
+
```html
|
|
913
|
+
<!-- The payload rides in a visually hidden span; the <article> is what
|
|
914
|
+
gets outlined and clicked. -->
|
|
915
|
+
<article data-prepr-edit-target>
|
|
916
|
+
<h2>Product title</h2>
|
|
917
|
+
<span hidden>{encodedPayload}</span>
|
|
918
|
+
</article>
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
Without the attribute that span is skipped entirely — it renders no box, so there is nothing to click.
|
|
922
|
+
|
|
923
|
+
It is equally useful when the encoded text sits deep inside markup you do not control, and the natural click target is an ancestor:
|
|
924
|
+
|
|
925
|
+
```html
|
|
926
|
+
<figure data-prepr-edit-target>
|
|
927
|
+
<img src="..." alt="..." />
|
|
928
|
+
<figcaption><em>{encodedCaption}</em></figcaption>
|
|
929
|
+
</figure>
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
Without it the `<em>` would be the editable element; with it the whole figure is.
|
|
887
933
|
|
|
888
934
|
## Examples
|
|
889
935
|
|
|
@@ -897,6 +943,14 @@ Runnable examples live in the repository root:
|
|
|
897
943
|
| `examples/nuxt` | `server/middleware`, `useAsyncData`, `.vue` components |
|
|
898
944
|
| `examples/express` | Vanilla core — a hand-written adapter for an unsupported framework |
|
|
899
945
|
|
|
946
|
+
## Migrating from `@preprio/prepr-nextjs`
|
|
947
|
+
|
|
948
|
+
`@preprio/toolkit` replaces the Next.js-only `@preprio/prepr-nextjs` package. Most of
|
|
949
|
+
the change is import paths — the middleware signature is unchanged and the server
|
|
950
|
+
helpers keep their names — but the toolbar's provider and the stylesheet are gone.
|
|
951
|
+
|
|
952
|
+
The [migration guide](https://github.com/preprio/prepr-toolkit/blob/main/MIGRATION.md) has the diffs.
|
|
953
|
+
|
|
900
954
|
## Support
|
|
901
955
|
|
|
902
956
|
- **Documentation**: [Prepr Documentation](https://docs.prepr.io)
|
package/dist/astro/index.cjs
CHANGED
package/dist/astro/index.js
CHANGED
|
@@ -169,6 +169,20 @@ function walkTextNodes(root, visit) {
|
|
|
169
169
|
visit(node);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
function resolveEditTarget(node) {
|
|
173
|
+
const parent = node.parentElement;
|
|
174
|
+
if (!parent) return null;
|
|
175
|
+
const editTarget = parent.closest("[data-prepr-edit-target]");
|
|
176
|
+
if (editTarget) return editTarget;
|
|
177
|
+
return isRenderedElement(parent) ? parent : null;
|
|
178
|
+
}
|
|
179
|
+
function isRenderedElement(element) {
|
|
180
|
+
if (element.hidden) return false;
|
|
181
|
+
if (element.getClientRects().length === 0) return false;
|
|
182
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
183
|
+
if (!style) return true;
|
|
184
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
185
|
+
}
|
|
172
186
|
function tagEncodedElement(element, decoded) {
|
|
173
187
|
element.setAttribute("data-prepr-encoded", "");
|
|
174
188
|
element.setAttribute("data-prepr-href", decoded.href);
|
|
@@ -192,7 +206,7 @@ var StegaElements = class {
|
|
|
192
206
|
tagFromTextNode(node) {
|
|
193
207
|
const decoded = this.decode(node.textContent);
|
|
194
208
|
if (!decoded?.href) return false;
|
|
195
|
-
const target = node
|
|
209
|
+
const target = resolveEditTarget(node);
|
|
196
210
|
if (!target || target.hasAttribute("data-prepr-encoded")) return false;
|
|
197
211
|
tagEncodedElement(target, decoded);
|
|
198
212
|
return true;
|
|
@@ -630,11 +644,7 @@ function createStegaAutoClean() {
|
|
|
630
644
|
if (!decoded?.href) return false;
|
|
631
645
|
cleaned.set(textNode, { original: textContent, cleaned: stripped });
|
|
632
646
|
textNode.textContent = stripped;
|
|
633
|
-
|
|
634
|
-
const editTargetParent = textNode.parentElement?.closest(
|
|
635
|
-
"[data-prepr-edit-target]"
|
|
636
|
-
);
|
|
637
|
-
if (editTargetParent) target = editTargetParent;
|
|
647
|
+
const target = resolveEditTarget(textNode);
|
|
638
648
|
if (target) {
|
|
639
649
|
tagEncodedElement(target, decoded);
|
|
640
650
|
return true;
|
package/dist/index.cjs
CHANGED
|
@@ -44,7 +44,7 @@ __export(src_exports, {
|
|
|
44
44
|
module.exports = __toCommonJS(src_exports);
|
|
45
45
|
|
|
46
46
|
// src/version.ts
|
|
47
|
-
var VERSION = "0.2
|
|
47
|
+
var VERSION = "0.3.2";
|
|
48
48
|
|
|
49
49
|
// src/core/features.ts
|
|
50
50
|
function isEnabled(config) {
|
|
@@ -420,6 +420,20 @@ function walkTextNodes(root, visit) {
|
|
|
420
420
|
visit(node);
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
|
+
function resolveEditTarget(node) {
|
|
424
|
+
const parent = node.parentElement;
|
|
425
|
+
if (!parent) return null;
|
|
426
|
+
const editTarget = parent.closest("[data-prepr-edit-target]");
|
|
427
|
+
if (editTarget) return editTarget;
|
|
428
|
+
return isRenderedElement(parent) ? parent : null;
|
|
429
|
+
}
|
|
430
|
+
function isRenderedElement(element) {
|
|
431
|
+
if (element.hidden) return false;
|
|
432
|
+
if (element.getClientRects().length === 0) return false;
|
|
433
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
434
|
+
if (!style) return true;
|
|
435
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
436
|
+
}
|
|
423
437
|
function tagEncodedElement(element, decoded) {
|
|
424
438
|
element.setAttribute("data-prepr-encoded", "");
|
|
425
439
|
element.setAttribute("data-prepr-href", decoded.href);
|
|
@@ -445,11 +459,7 @@ function createStegaAutoClean() {
|
|
|
445
459
|
if (!decoded?.href) return false;
|
|
446
460
|
cleaned.set(textNode, { original: textContent, cleaned: stripped });
|
|
447
461
|
textNode.textContent = stripped;
|
|
448
|
-
|
|
449
|
-
const editTargetParent = textNode.parentElement?.closest(
|
|
450
|
-
"[data-prepr-edit-target]"
|
|
451
|
-
);
|
|
452
|
-
if (editTargetParent) target = editTargetParent;
|
|
462
|
+
const target = resolveEditTarget(textNode);
|
|
453
463
|
if (target) {
|
|
454
464
|
tagEncodedElement(target, decoded);
|
|
455
465
|
return true;
|
|
@@ -545,7 +555,7 @@ var StegaElements = class {
|
|
|
545
555
|
tagFromTextNode(node) {
|
|
546
556
|
const decoded = this.decode(node.textContent);
|
|
547
557
|
if (!decoded?.href) return false;
|
|
548
|
-
const target = node
|
|
558
|
+
const target = resolveEditTarget(node);
|
|
549
559
|
if (!target || target.hasAttribute("data-prepr-encoded")) return false;
|
|
550
560
|
tagEncodedElement(target, decoded);
|
|
551
561
|
return true;
|
package/dist/index.d.cts
CHANGED
|
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
|
|
|
5
5
|
* Package version; must match package.json. Lives in its own leaf module so
|
|
6
6
|
* core can import it without cycling through the barrel.
|
|
7
7
|
*/
|
|
8
|
-
declare const VERSION = "0.2
|
|
8
|
+
declare const VERSION = "0.3.2";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
|
|
|
5
5
|
* Package version; must match package.json. Lives in its own leaf module so
|
|
6
6
|
* core can import it without cycling through the barrel.
|
|
7
7
|
*/
|
|
8
|
-
declare const VERSION = "0.2
|
|
8
|
+
declare const VERSION = "0.3.2";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
|
package/dist/index.js
CHANGED
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
setTrackingParam,
|
|
18
18
|
stegaClean,
|
|
19
19
|
trackEvent
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-GIEOTQD4.js";
|
|
21
21
|
|
|
22
22
|
// src/version.ts
|
|
23
|
-
var VERSION = "0.2
|
|
23
|
+
var VERSION = "0.3.2";
|
|
24
24
|
|
|
25
25
|
// src/core/server.ts
|
|
26
26
|
var PreprError = class extends Error {
|
|
@@ -396,6 +396,20 @@ function walkTextNodes(root, visit) {
|
|
|
396
396
|
visit(node);
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
|
+
function resolveEditTarget(node) {
|
|
400
|
+
const parent = node.parentElement;
|
|
401
|
+
if (!parent) return null;
|
|
402
|
+
const editTarget = parent.closest("[data-prepr-edit-target]");
|
|
403
|
+
if (editTarget) return editTarget;
|
|
404
|
+
return isRenderedElement(parent) ? parent : null;
|
|
405
|
+
}
|
|
406
|
+
function isRenderedElement(element) {
|
|
407
|
+
if (element.hidden) return false;
|
|
408
|
+
if (element.getClientRects().length === 0) return false;
|
|
409
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
410
|
+
if (!style) return true;
|
|
411
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
412
|
+
}
|
|
399
413
|
function tagEncodedElement(element, decoded) {
|
|
400
414
|
element.setAttribute("data-prepr-encoded", "");
|
|
401
415
|
element.setAttribute("data-prepr-href", decoded.href);
|
|
@@ -421,11 +435,7 @@ function createStegaAutoClean() {
|
|
|
421
435
|
if (!decoded?.href) return false;
|
|
422
436
|
cleaned.set(textNode, { original: textContent, cleaned: stripped });
|
|
423
437
|
textNode.textContent = stripped;
|
|
424
|
-
|
|
425
|
-
const editTargetParent = textNode.parentElement?.closest(
|
|
426
|
-
"[data-prepr-edit-target]"
|
|
427
|
-
);
|
|
428
|
-
if (editTargetParent) target = editTargetParent;
|
|
438
|
+
const target = resolveEditTarget(textNode);
|
|
429
439
|
if (target) {
|
|
430
440
|
tagEncodedElement(target, decoded);
|
|
431
441
|
return true;
|
|
@@ -521,7 +531,7 @@ var StegaElements = class {
|
|
|
521
531
|
tagFromTextNode(node) {
|
|
522
532
|
const decoded = this.decode(node.textContent);
|
|
523
533
|
if (!decoded?.href) return false;
|
|
524
|
-
const target = node
|
|
534
|
+
const target = resolveEditTarget(node);
|
|
525
535
|
if (!target || target.hasAttribute("data-prepr-encoded")) return false;
|
|
526
536
|
tagEncodedElement(target, decoded);
|
|
527
537
|
return true;
|
package/dist/nextjs/index.cjs
CHANGED
package/dist/nextjs/index.js
CHANGED
package/dist/nuxt/index.cjs
CHANGED
package/dist/nuxt/index.js
CHANGED
|
@@ -395,6 +395,20 @@ function walkTextNodes(root, visit) {
|
|
|
395
395
|
visit(node);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
+
function resolveEditTarget(node) {
|
|
399
|
+
const parent = node.parentElement;
|
|
400
|
+
if (!parent) return null;
|
|
401
|
+
const editTarget = parent.closest("[data-prepr-edit-target]");
|
|
402
|
+
if (editTarget) return editTarget;
|
|
403
|
+
return isRenderedElement(parent) ? parent : null;
|
|
404
|
+
}
|
|
405
|
+
function isRenderedElement(element) {
|
|
406
|
+
if (element.hidden) return false;
|
|
407
|
+
if (element.getClientRects().length === 0) return false;
|
|
408
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
409
|
+
if (!style) return true;
|
|
410
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
411
|
+
}
|
|
398
412
|
function tagEncodedElement(element, decoded) {
|
|
399
413
|
element.setAttribute("data-prepr-encoded", "");
|
|
400
414
|
element.setAttribute("data-prepr-href", decoded.href);
|
|
@@ -420,11 +434,7 @@ function createStegaAutoClean() {
|
|
|
420
434
|
if (!decoded?.href) return false;
|
|
421
435
|
cleaned.set(textNode, { original: textContent, cleaned: stripped });
|
|
422
436
|
textNode.textContent = stripped;
|
|
423
|
-
|
|
424
|
-
const editTargetParent = textNode.parentElement?.closest(
|
|
425
|
-
"[data-prepr-edit-target]"
|
|
426
|
-
);
|
|
427
|
-
if (editTargetParent) target = editTargetParent;
|
|
437
|
+
const target = resolveEditTarget(textNode);
|
|
428
438
|
if (target) {
|
|
429
439
|
tagEncodedElement(target, decoded);
|
|
430
440
|
return true;
|
|
@@ -520,7 +530,7 @@ var StegaElements = class {
|
|
|
520
530
|
tagFromTextNode(node) {
|
|
521
531
|
const decoded = this.decode(node.textContent);
|
|
522
532
|
if (!decoded?.href) return false;
|
|
523
|
-
const target = node
|
|
533
|
+
const target = resolveEditTarget(node);
|
|
524
534
|
if (!target || target.hasAttribute("data-prepr-encoded")) return false;
|
|
525
535
|
tagEncodedElement(target, decoded);
|
|
526
536
|
return true;
|
package/dist/react/components.js
CHANGED
|
@@ -371,6 +371,20 @@ function walkTextNodes(root, visit) {
|
|
|
371
371
|
visit(node);
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
|
+
function resolveEditTarget(node) {
|
|
375
|
+
const parent = node.parentElement;
|
|
376
|
+
if (!parent) return null;
|
|
377
|
+
const editTarget = parent.closest("[data-prepr-edit-target]");
|
|
378
|
+
if (editTarget) return editTarget;
|
|
379
|
+
return isRenderedElement(parent) ? parent : null;
|
|
380
|
+
}
|
|
381
|
+
function isRenderedElement(element) {
|
|
382
|
+
if (element.hidden) return false;
|
|
383
|
+
if (element.getClientRects().length === 0) return false;
|
|
384
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
385
|
+
if (!style) return true;
|
|
386
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
387
|
+
}
|
|
374
388
|
function tagEncodedElement(element, decoded) {
|
|
375
389
|
element.setAttribute("data-prepr-encoded", "");
|
|
376
390
|
element.setAttribute("data-prepr-href", decoded.href);
|
|
@@ -396,11 +410,7 @@ function createStegaAutoClean() {
|
|
|
396
410
|
if (!decoded?.href) return false;
|
|
397
411
|
cleaned.set(textNode, { original: textContent, cleaned: stripped });
|
|
398
412
|
textNode.textContent = stripped;
|
|
399
|
-
|
|
400
|
-
const editTargetParent = textNode.parentElement?.closest(
|
|
401
|
-
"[data-prepr-edit-target]"
|
|
402
|
-
);
|
|
403
|
-
if (editTargetParent) target = editTargetParent;
|
|
413
|
+
const target = resolveEditTarget(textNode);
|
|
404
414
|
if (target) {
|
|
405
415
|
tagEncodedElement(target, decoded);
|
|
406
416
|
return true;
|
|
@@ -496,7 +506,7 @@ var StegaElements = class {
|
|
|
496
506
|
tagFromTextNode(node) {
|
|
497
507
|
const decoded = this.decode(node.textContent);
|
|
498
508
|
if (!decoded?.href) return false;
|
|
499
|
-
const target = node
|
|
509
|
+
const target = resolveEditTarget(node);
|
|
500
510
|
if (!target || target.hasAttribute("data-prepr-encoded")) return false;
|
|
501
511
|
tagEncodedElement(target, decoded);
|
|
502
512
|
return true;
|
package/dist/sveltekit/index.cjs
CHANGED
package/dist/sveltekit/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preprio/toolkit",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Prepr personalization, A/B testing and preview toolkit for React, Next.js, Nuxt, Astro, SvelteKit and Express",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Prepr B.V. (https://prepr.io)",
|