@simmalugnt-se/payload-visual-editing 0.1.0
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/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/admin/VisualEditingAdminBridge.d.ts +7 -0
- package/dist/admin/VisualEditingAdminBridge.js +310 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +1 -0
- package/dist/exports/frontend.d.ts +2 -0
- package/dist/exports/frontend.js +2 -0
- package/dist/frontend/VisualEditingPreview.d.ts +10 -0
- package/dist/frontend/VisualEditingPreview.js +256 -0
- package/dist/frontend/editable.d.ts +21 -0
- package/dist/frontend/editable.js +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/labels.d.ts +17 -0
- package/dist/labels.js +73 -0
- package/dist/package-name.d.ts +2 -0
- package/dist/package-name.js +2 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +30 -0
- package/dist/protocol.d.ts +92 -0
- package/dist/protocol.js +184 -0
- package/dist/reveal-plan.d.ts +45 -0
- package/dist/reveal-plan.js +122 -0
- package/dist/styles/admin.css +19 -0
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +3 -0
- package/package.json +70 -0
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/** Shared between the preview (iframe) and Admin. No React, no DOM access. */
|
|
2
|
+
export const MESSAGE_TYPE = "payload-visual-editing";
|
|
3
|
+
export const ATTR_BLOCK = "data-payload-block";
|
|
4
|
+
/** Block slug; the preview looks up the label Admin sent for it. */
|
|
5
|
+
export const ATTR_BLOCK_TYPE = "data-payload-block-type";
|
|
6
|
+
/** The editor's own block name, which Admin also prefers over the block label in row headers. */
|
|
7
|
+
export const ATTR_BLOCK_NAME = "data-payload-block-name";
|
|
8
|
+
export const ATTR_FIELD = "data-payload-field";
|
|
9
|
+
/** On the rich text container whose direct children are the top-level editor nodes. */
|
|
10
|
+
export const ATTR_RICHTEXT = "data-payload-richtext";
|
|
11
|
+
const MAX_NODE_TEXT = 200;
|
|
12
|
+
export function isSelectMessage(value) {
|
|
13
|
+
if (!value || typeof value !== "object") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const message = value;
|
|
17
|
+
return (message.type === MESSAGE_TYPE &&
|
|
18
|
+
message.action === "select" &&
|
|
19
|
+
typeof message.blockId === "string" &&
|
|
20
|
+
message.blockId.length > 0 &&
|
|
21
|
+
(message.field === undefined || isFieldName(message.field)) &&
|
|
22
|
+
(message.node === undefined || isNode(message.node)));
|
|
23
|
+
}
|
|
24
|
+
function isNode(value) {
|
|
25
|
+
if (!value || typeof value !== "object") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const node = value;
|
|
29
|
+
return (Number.isInteger(node.index) &&
|
|
30
|
+
node.index >= 0 &&
|
|
31
|
+
(node.text === undefined ||
|
|
32
|
+
(typeof node.text === "string" && node.text.length <= MAX_NODE_TEXT)));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pick the editor node matching the clicked preview node. Text wins over index because
|
|
36
|
+
* converters may render a node as zero or several elements, shifting indexes.
|
|
37
|
+
*/
|
|
38
|
+
export function matchNodeIndex(nodeTexts, node) {
|
|
39
|
+
const wanted = normalize(node.text);
|
|
40
|
+
if (wanted) {
|
|
41
|
+
const exact = nodeTexts.findIndex((text) => normalize(text) === wanted);
|
|
42
|
+
if (exact !== -1) {
|
|
43
|
+
return exact;
|
|
44
|
+
}
|
|
45
|
+
const prefix = nodeTexts.findIndex((text) => normalize(text).startsWith(wanted));
|
|
46
|
+
if (prefix !== -1) {
|
|
47
|
+
return prefix;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return Math.min(node.index, Math.max(nodeTexts.length - 1, 0));
|
|
51
|
+
}
|
|
52
|
+
function normalize(text) {
|
|
53
|
+
return (text ?? "").replace(/\s+/g, " ").trim();
|
|
54
|
+
}
|
|
55
|
+
/** Preview text sent with a node: enough to recognise it, never the whole document. */
|
|
56
|
+
export function nodeSnippet(text) {
|
|
57
|
+
const value = normalize(text ?? undefined).slice(0, 80);
|
|
58
|
+
return value || undefined;
|
|
59
|
+
}
|
|
60
|
+
/** A single field name or a dotted path inside the block; never array syntax or traversal. */
|
|
61
|
+
function isFieldName(value) {
|
|
62
|
+
return typeof value === "string" && /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(value);
|
|
63
|
+
}
|
|
64
|
+
export function isFocusMessage(value) {
|
|
65
|
+
if (!value || typeof value !== "object") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const message = value;
|
|
69
|
+
return (message.type === MESSAGE_TYPE &&
|
|
70
|
+
message.action === "focus" &&
|
|
71
|
+
Array.isArray(message.targets) &&
|
|
72
|
+
message.targets.length <= 10 &&
|
|
73
|
+
message.targets.every((target) => target &&
|
|
74
|
+
typeof target === "object" &&
|
|
75
|
+
typeof target.blockId === "string" &&
|
|
76
|
+
target.blockId.length > 0 &&
|
|
77
|
+
(target.field === undefined || isFieldName(target.field))));
|
|
78
|
+
}
|
|
79
|
+
export function isReadyMessage(value) {
|
|
80
|
+
return (!!value &&
|
|
81
|
+
typeof value === "object" &&
|
|
82
|
+
value.type === MESSAGE_TYPE &&
|
|
83
|
+
value.action === "ready");
|
|
84
|
+
}
|
|
85
|
+
const MAX_LABELED_BLOCKS = 500;
|
|
86
|
+
const MAX_LABELED_FIELDS = 500;
|
|
87
|
+
const MAX_LABEL = 200;
|
|
88
|
+
export function isLabelsMessage(value) {
|
|
89
|
+
if (!value || typeof value !== "object") {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const message = value;
|
|
93
|
+
if (message.type !== MESSAGE_TYPE || message.action !== "labels" || !isRecord(message.blocks)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const blocks = Object.values(message.blocks);
|
|
97
|
+
return (blocks.length <= MAX_LABELED_BLOCKS &&
|
|
98
|
+
blocks.every((block) => {
|
|
99
|
+
if (!isRecord(block) || !isRecord(block.fields)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const fields = Object.values(block.fields);
|
|
103
|
+
return ((block.label === undefined || isLabel(block.label)) &&
|
|
104
|
+
fields.length <= MAX_LABELED_FIELDS &&
|
|
105
|
+
fields.every(isLabel));
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
function isRecord(value) {
|
|
109
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
110
|
+
}
|
|
111
|
+
function isLabel(value) {
|
|
112
|
+
return typeof value === "string" && value.length <= MAX_LABEL;
|
|
113
|
+
}
|
|
114
|
+
/** Inverse of `fieldDomId`: "field-layout__0__headline" → "layout.0.headline". */
|
|
115
|
+
export function pathFromDomId(id) {
|
|
116
|
+
return id.startsWith("field-") && id.length > "field-".length
|
|
117
|
+
? id.slice("field-".length).replace(/__/g, ".")
|
|
118
|
+
: undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Rows enclosing a form path, innermost first, with the rest of the path as the field.
|
|
122
|
+
* "layout.1.items.0.title" → the items row (field "title"), then the layout row (field undefined:
|
|
123
|
+
* "items.0.title" is not a plain field name the preview could have marked).
|
|
124
|
+
*/
|
|
125
|
+
export function focusTargets(path, fields) {
|
|
126
|
+
const segments = path.split(".");
|
|
127
|
+
const targets = [];
|
|
128
|
+
for (let i = segments.length - 1; i > 0; i -= 1) {
|
|
129
|
+
if (!/^\d+$/.test(segments[i] ?? "")) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const rowPath = segments.slice(0, i + 1).join(".");
|
|
133
|
+
const id = fields[`${rowPath}.id`]?.value;
|
|
134
|
+
if (typeof id !== "string" || !id) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const rest = segments.slice(i + 1).join(".");
|
|
138
|
+
targets.push(rest && isFieldName(rest) ? { blockId: id, field: rest } : { blockId: id });
|
|
139
|
+
}
|
|
140
|
+
return targets;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Find the block row whose `id` matches, using Payload form state keys ("layout.2.id").
|
|
144
|
+
* Ids survive reordering in the unsaved form; indexes from the preview would not.
|
|
145
|
+
*/
|
|
146
|
+
export function resolveBlockTarget(fields, blockId) {
|
|
147
|
+
for (const [path, field] of Object.entries(fields)) {
|
|
148
|
+
if (!path.endsWith(".id") || field?.value !== blockId) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const rowPath = path.slice(0, -".id".length);
|
|
152
|
+
const rows = rowSteps(rowPath);
|
|
153
|
+
if (rows.length === 0) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
return { rowPath, rows };
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function rowSteps(rowPath) {
|
|
161
|
+
const segments = rowPath.split(".");
|
|
162
|
+
const steps = [];
|
|
163
|
+
for (let i = 1; i < segments.length; i += 1) {
|
|
164
|
+
const segment = segments[i] ?? "";
|
|
165
|
+
if (/^\d+$/.test(segment)) {
|
|
166
|
+
steps.push({ parentPath: segments.slice(0, i).join("."), index: Number(segment) });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const last = segments.at(-1) ?? "";
|
|
170
|
+
return /^\d+$/.test(last) ? steps : [];
|
|
171
|
+
}
|
|
172
|
+
/** DOM id Payload gives field inputs: "layout.0.headline" → "field-layout__0__headline". */
|
|
173
|
+
export function fieldDomId(path) {
|
|
174
|
+
return `field-${path.replace(/\./g, "__")}`;
|
|
175
|
+
}
|
|
176
|
+
/** Inverse of `rowDomId`: "layout-0-items-row-1" → "layout.0.items.1". */
|
|
177
|
+
export function pathFromRowDomId(id) {
|
|
178
|
+
const match = /^(.+)-row-(\d+)$/.exec(id);
|
|
179
|
+
return match ? `${match[1]?.split("-").join(".")}.${match[2]}` : undefined;
|
|
180
|
+
}
|
|
181
|
+
/** DOM id Payload gives block and array rows: ("layout", 0) → "layout-row-0". */
|
|
182
|
+
export function rowDomId(step) {
|
|
183
|
+
return `${step.parentPath.split(".").join("-")}-row-${step.index}`;
|
|
184
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work out which tabs must be opened to show a form path. Payload only renders the active tab,
|
|
3
|
+
* so a field in another tab has no DOM element until its tab button is clicked.
|
|
4
|
+
* Pure: takes client field config, returns steps for the Admin bridge to perform.
|
|
5
|
+
*/
|
|
6
|
+
export type FieldLike = {
|
|
7
|
+
type: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
label?: unknown;
|
|
10
|
+
fields?: FieldLike[];
|
|
11
|
+
tabs?: Array<{
|
|
12
|
+
name?: string;
|
|
13
|
+
fields?: FieldLike[];
|
|
14
|
+
}>;
|
|
15
|
+
blocks?: BlockLike[];
|
|
16
|
+
blockReferences?: Array<BlockLike | string>;
|
|
17
|
+
};
|
|
18
|
+
export type BlockLike = {
|
|
19
|
+
slug: string;
|
|
20
|
+
labels?: {
|
|
21
|
+
singular?: unknown;
|
|
22
|
+
};
|
|
23
|
+
fields?: FieldLike[];
|
|
24
|
+
};
|
|
25
|
+
export type RevealStep =
|
|
26
|
+
/** Click tab `index` of the `ordinal`-th tabs field in the current scope. The tab content becomes the scope. */
|
|
27
|
+
{
|
|
28
|
+
kind: "tab";
|
|
29
|
+
ordinal: number;
|
|
30
|
+
index: number;
|
|
31
|
+
}
|
|
32
|
+
/** Enter a block or array row. The row element becomes the scope. */
|
|
33
|
+
| {
|
|
34
|
+
kind: "row";
|
|
35
|
+
parentPath: string;
|
|
36
|
+
index: number;
|
|
37
|
+
};
|
|
38
|
+
type Lookup = {
|
|
39
|
+
/** Block slug of the row at a form path, e.g. "layout.0" → "hero". */
|
|
40
|
+
blockTypeAt: (rowPath: string) => string | undefined;
|
|
41
|
+
/** Blocks declared once in config and referenced by slug. */
|
|
42
|
+
blocksBySlug?: Record<string, BlockLike | undefined>;
|
|
43
|
+
};
|
|
44
|
+
export declare function planReveal(rootFields: FieldLike[], path: string, lookup: Lookup): RevealStep[] | null;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work out which tabs must be opened to show a form path. Payload only renders the active tab,
|
|
3
|
+
* so a field in another tab has no DOM element until its tab button is clicked.
|
|
4
|
+
* Pure: takes client field config, returns steps for the Admin bridge to perform.
|
|
5
|
+
*/
|
|
6
|
+
export function planReveal(rootFields, path, lookup) {
|
|
7
|
+
const segments = path.split(".");
|
|
8
|
+
const steps = [];
|
|
9
|
+
let fields = rootFields;
|
|
10
|
+
let counter = { value: 0 };
|
|
11
|
+
let walked = [];
|
|
12
|
+
for (let i = 0; i < segments.length;) {
|
|
13
|
+
const segment = segments[i] ?? "";
|
|
14
|
+
const found = findIn(fields, segment, counter);
|
|
15
|
+
if (!found) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
steps.push(...found.tabs);
|
|
19
|
+
counter = found.counter;
|
|
20
|
+
walked = [...walked, segment];
|
|
21
|
+
i += 1;
|
|
22
|
+
if (found.kind === "namedTab") {
|
|
23
|
+
fields = found.fields;
|
|
24
|
+
counter = { value: 0 };
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const field = found.field;
|
|
28
|
+
const next = segments[i];
|
|
29
|
+
if ((field.type === "blocks" || field.type === "array") &&
|
|
30
|
+
next !== undefined &&
|
|
31
|
+
/^\d+$/.test(next)) {
|
|
32
|
+
const parentPath = walked.join(".");
|
|
33
|
+
steps.push({ kind: "row", parentPath, index: Number(next) });
|
|
34
|
+
walked = [...walked, next];
|
|
35
|
+
i += 1;
|
|
36
|
+
counter = { value: 0 };
|
|
37
|
+
if (field.type === "array") {
|
|
38
|
+
fields = field.fields ?? [];
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const block = findBlock(field, lookup.blockTypeAt(walked.join(".")), lookup.blocksBySlug);
|
|
42
|
+
if (!block) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
fields = block.fields ?? [];
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (field.fields) {
|
|
49
|
+
// Named group: same DOM scope, keep counting tabs.
|
|
50
|
+
fields = field.fields;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// A leaf; anything after it (e.g. rich text internals) is not a form field.
|
|
54
|
+
return i === segments.length ? steps : null;
|
|
55
|
+
}
|
|
56
|
+
return steps;
|
|
57
|
+
}
|
|
58
|
+
function findIn(fields, name, counter) {
|
|
59
|
+
for (const field of fields) {
|
|
60
|
+
if (field.type === "tabs") {
|
|
61
|
+
const ordinal = counter.value;
|
|
62
|
+
counter.value += 1;
|
|
63
|
+
for (const [index, tab] of (field.tabs ?? []).entries()) {
|
|
64
|
+
const step = { kind: "tab", ordinal, index };
|
|
65
|
+
if (tab.name === name) {
|
|
66
|
+
return {
|
|
67
|
+
kind: "namedTab",
|
|
68
|
+
fields: tab.fields ?? [],
|
|
69
|
+
tabs: [step],
|
|
70
|
+
counter: { value: 0 },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!tab.name) {
|
|
74
|
+
const inner = findIn(tab.fields ?? [], name, { value: 0 });
|
|
75
|
+
if (inner) {
|
|
76
|
+
return { ...inner, tabs: [step, ...inner.tabs] };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (field.name === name) {
|
|
83
|
+
return { kind: "field", field, tabs: [], counter };
|
|
84
|
+
}
|
|
85
|
+
if (!field.name && field.fields) {
|
|
86
|
+
// Row, collapsible or unnamed group: transparent for paths.
|
|
87
|
+
const inner = findIn(field.fields, name, counter);
|
|
88
|
+
if (inner) {
|
|
89
|
+
return inner;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
counter.value += countTabs(field);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/** Tabs fields rendered inside a field that is skipped, so later ordinals stay aligned with the DOM. */
|
|
98
|
+
function countTabs(field) {
|
|
99
|
+
if (field.type === "tabs") {
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
if (field.type === "blocks" || field.type === "array" || !field.fields) {
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
return field.fields.reduce((sum, child) => sum + countTabs(child), 0);
|
|
106
|
+
}
|
|
107
|
+
function findBlock(field, slug, blocksBySlug) {
|
|
108
|
+
if (!slug) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const inline = field.blocks?.find((block) => block.slug === slug);
|
|
112
|
+
if (inline) {
|
|
113
|
+
return inline;
|
|
114
|
+
}
|
|
115
|
+
for (const reference of field.blockReferences ?? []) {
|
|
116
|
+
const block = typeof reference === "string" ? blocksBySlug?.[reference] : reference;
|
|
117
|
+
if (block?.slug === slug) {
|
|
118
|
+
return block;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Keep in sync with HIGHLIGHT_COLOR / HIGHLIGHT_TINT in src/theme.ts (the preview overlay colours). */
|
|
2
|
+
.pve-highlight {
|
|
3
|
+
border-radius: 4px;
|
|
4
|
+
animation: pve-highlight 1.6s ease-out;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@keyframes pve-highlight {
|
|
8
|
+
0%,
|
|
9
|
+
40% {
|
|
10
|
+
outline: 2px solid #2563eb;
|
|
11
|
+
outline-offset: 4px;
|
|
12
|
+
background-color: rgba(37, 99, 235, 0.12);
|
|
13
|
+
}
|
|
14
|
+
100% {
|
|
15
|
+
outline: 2px solid transparent;
|
|
16
|
+
outline-offset: 4px;
|
|
17
|
+
background-color: transparent;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/theme.d.ts
ADDED
package/dist/theme.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@simmalugnt-se/payload-visual-editing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Click a block in Payload Live Preview to open its fields in the edit form",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"payload",
|
|
7
|
+
"payload-plugin"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "Simmalugnt AB",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/simmalugnt-se/payload-plugins.git",
|
|
14
|
+
"directory": "packages/payload-visual-editing"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": [
|
|
24
|
+
"*.css"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./client": {
|
|
33
|
+
"import": "./dist/exports/client.js",
|
|
34
|
+
"types": "./dist/exports/client.d.ts",
|
|
35
|
+
"default": "./dist/exports/client.js"
|
|
36
|
+
},
|
|
37
|
+
"./frontend": {
|
|
38
|
+
"import": "./dist/exports/frontend.js",
|
|
39
|
+
"types": "./dist/exports/frontend.d.ts",
|
|
40
|
+
"default": "./dist/exports/frontend.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@payloadcms/ui": ">=3.85.2 <4",
|
|
48
|
+
"payload": ">=3.85.2 <4",
|
|
49
|
+
"react": "^19.0.0",
|
|
50
|
+
"react-dom": "^19.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@payloadcms/ui": "3.87.1",
|
|
54
|
+
"@types/node": "^22",
|
|
55
|
+
"@types/react": "^19.2.18",
|
|
56
|
+
"@types/react-dom": "^19.2.4",
|
|
57
|
+
"payload": "3.87.1",
|
|
58
|
+
"react": "19.2.8",
|
|
59
|
+
"react-dom": "19.2.8",
|
|
60
|
+
"tsx": "^4.21.0",
|
|
61
|
+
"typescript": "5.9.3"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp -R src/styles dist/styles",
|
|
65
|
+
"test": "tsx --test tests/*.test.ts",
|
|
66
|
+
"typecheck": "tsc --noEmit"
|
|
67
|
+
},
|
|
68
|
+
"main": "./dist/index.js",
|
|
69
|
+
"types": "./dist/index.d.ts"
|
|
70
|
+
}
|