@plenary/react 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/dist/bridge.d.ts +34 -0
- package/dist/bridge.js +452 -0
- package/dist/bridge.js.map +1 -0
- package/dist/components/Box.d.ts +14 -0
- package/dist/components/Box.js +8 -0
- package/dist/components/Box.js.map +1 -0
- package/dist/components/Form.d.ts +36 -0
- package/dist/components/Form.js +280 -0
- package/dist/components/Form.js.map +1 -0
- package/dist/components/Image.d.ts +25 -0
- package/dist/components/Image.js +116 -0
- package/dist/components/Image.js.map +1 -0
- package/dist/components/Placeholder.d.ts +12 -0
- package/dist/components/Placeholder.js +24 -0
- package/dist/components/Placeholder.js.map +1 -0
- package/dist/components/Section.d.ts +20 -0
- package/dist/components/Section.js +15 -0
- package/dist/components/Section.js.map +1 -0
- package/dist/components/Text.d.ts +15 -0
- package/dist/components/Text.js +8 -0
- package/dist/components/Text.js.map +1 -0
- package/dist/components/Video.d.ts +48 -0
- package/dist/components/Video.js +131 -0
- package/dist/components/Video.js.map +1 -0
- package/dist/context.d.ts +26 -0
- package/dist/context.js +20 -0
- package/dist/context.js.map +1 -0
- package/dist/form.d.ts +25 -0
- package/dist/form.js +11 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +60 -0
- package/dist/media.js +74 -0
- package/dist/media.js.map +1 -0
- package/dist/registry.d.ts +45 -0
- package/dist/registry.js +232 -0
- package/dist/registry.js.map +1 -0
- package/dist/render.d.ts +46 -0
- package/dist/render.js +179 -0
- package/dist/render.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef, useState } from "react";
|
|
4
|
+
import { PlenaryError } from "@plenary/client";
|
|
5
|
+
import { usePlenaryClient } from "../context.js";
|
|
6
|
+
const UNSUPPORTED_TYPES = /* @__PURE__ */ new Set(["object", "list", "json"]);
|
|
7
|
+
const FIELD_ERROR_MESSAGES = {
|
|
8
|
+
entry_field_required: "This field is required.",
|
|
9
|
+
entry_field_required_nonempty: "This field is required.",
|
|
10
|
+
entry_field_value_required: "This field is required.",
|
|
11
|
+
entry_field_value_taken: "This value is already taken."
|
|
12
|
+
};
|
|
13
|
+
function FormPlaceholder({ className, style }) {
|
|
14
|
+
return /* @__PURE__ */ jsxs(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
"data-plenary-form-placeholder": "",
|
|
18
|
+
className,
|
|
19
|
+
style: {
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "center",
|
|
23
|
+
gap: 8,
|
|
24
|
+
minHeight: 120,
|
|
25
|
+
border: "1px dashed #94a3b8",
|
|
26
|
+
borderRadius: 4,
|
|
27
|
+
background: "#f1f5f9",
|
|
28
|
+
color: "#94a3b8",
|
|
29
|
+
...style
|
|
30
|
+
},
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsxs(
|
|
33
|
+
"svg",
|
|
34
|
+
{
|
|
35
|
+
width: "32",
|
|
36
|
+
height: "32",
|
|
37
|
+
viewBox: "0 0 24 24",
|
|
38
|
+
fill: "none",
|
|
39
|
+
stroke: "currentColor",
|
|
40
|
+
strokeWidth: "1.5",
|
|
41
|
+
strokeLinecap: "round",
|
|
42
|
+
strokeLinejoin: "round",
|
|
43
|
+
"aria-hidden": "true",
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ jsx("rect", { x: "4", y: "3", width: "16", height: "18", rx: "2" }),
|
|
46
|
+
/* @__PURE__ */ jsx("path", { d: "M8 8h8M8 12h8M8 16h4" })
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: "Choose a form in the Properties tab" })
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function collectValues(fields, formEl) {
|
|
56
|
+
const fd = new FormData(formEl);
|
|
57
|
+
const data = {};
|
|
58
|
+
for (const f of fields) {
|
|
59
|
+
if (UNSUPPORTED_TYPES.has(f.type)) continue;
|
|
60
|
+
switch (f.type) {
|
|
61
|
+
case "boolean":
|
|
62
|
+
data[f.key] = fd.get(f.key) === "on";
|
|
63
|
+
break;
|
|
64
|
+
case "multi_select": {
|
|
65
|
+
const values = fd.getAll(f.key).map(String);
|
|
66
|
+
if (values.length) data[f.key] = values;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case "tags": {
|
|
70
|
+
const values = String(fd.get(f.key) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
71
|
+
if (values.length) data[f.key] = values;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "number": {
|
|
75
|
+
const raw = String(fd.get(f.key) ?? "").trim();
|
|
76
|
+
if (raw !== "") data[f.key] = Number(raw);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
default: {
|
|
80
|
+
const raw = String(fd.get(f.key) ?? "");
|
|
81
|
+
if (raw !== "") data[f.key] = raw;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
function FieldInput({ field, id }) {
|
|
88
|
+
const ui = field.ui ?? {};
|
|
89
|
+
const validation = field.validation ?? {};
|
|
90
|
+
const common = {
|
|
91
|
+
id,
|
|
92
|
+
name: field.key,
|
|
93
|
+
required: field.required,
|
|
94
|
+
className: "plenary-form__input",
|
|
95
|
+
placeholder: ui.placeholder || void 0
|
|
96
|
+
};
|
|
97
|
+
switch (field.type) {
|
|
98
|
+
case "text":
|
|
99
|
+
if (ui.control === "textarea") {
|
|
100
|
+
return /* @__PURE__ */ jsx("textarea", { ...common, maxLength: validation.max_length });
|
|
101
|
+
}
|
|
102
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "text", maxLength: validation.max_length });
|
|
103
|
+
case "email":
|
|
104
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "email" });
|
|
105
|
+
case "url":
|
|
106
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "url" });
|
|
107
|
+
case "phone":
|
|
108
|
+
return /* @__PURE__ */ jsx(
|
|
109
|
+
"input",
|
|
110
|
+
{
|
|
111
|
+
...common,
|
|
112
|
+
type: "tel",
|
|
113
|
+
pattern: "\\+[1-9][0-9]{1,14}",
|
|
114
|
+
title: "International format, e.g. +15551234567",
|
|
115
|
+
placeholder: ui.placeholder || "+15551234567"
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
case "number":
|
|
119
|
+
return /* @__PURE__ */ jsx(
|
|
120
|
+
"input",
|
|
121
|
+
{
|
|
122
|
+
...common,
|
|
123
|
+
type: "number",
|
|
124
|
+
min: validation.min_val,
|
|
125
|
+
max: validation.max_val,
|
|
126
|
+
step: validation.multiple_of ?? "any"
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
case "date":
|
|
130
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "date" });
|
|
131
|
+
case "time":
|
|
132
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "time" });
|
|
133
|
+
case "datetime":
|
|
134
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "datetime-local" });
|
|
135
|
+
case "boolean":
|
|
136
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "checkbox", className: "plenary-form__checkbox" });
|
|
137
|
+
case "select": {
|
|
138
|
+
const options = (field.settings?.options ?? []).filter(
|
|
139
|
+
(o) => o && o.value !== void 0
|
|
140
|
+
);
|
|
141
|
+
if (ui.control === "radio" || ui.control === "segmented") {
|
|
142
|
+
return /* @__PURE__ */ jsx("div", { className: "plenary-form__options", role: "radiogroup", children: options.map((o, i) => /* @__PURE__ */ jsxs("label", { className: "plenary-form__option", children: [
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"input",
|
|
145
|
+
{
|
|
146
|
+
type: "radio",
|
|
147
|
+
name: field.key,
|
|
148
|
+
value: o.value,
|
|
149
|
+
required: field.required && i === 0
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
" ",
|
|
153
|
+
o.label
|
|
154
|
+
] }, o.value)) });
|
|
155
|
+
}
|
|
156
|
+
return /* @__PURE__ */ jsxs("select", { ...common, children: [
|
|
157
|
+
!field.required && /* @__PURE__ */ jsx("option", { value: "" }),
|
|
158
|
+
options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
case "multi_select": {
|
|
162
|
+
const options = field.settings?.options ?? [];
|
|
163
|
+
return /* @__PURE__ */ jsx("div", { className: "plenary-form__options", children: options.map((o) => /* @__PURE__ */ jsxs("label", { className: "plenary-form__option", children: [
|
|
164
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", name: field.key, value: o.value }),
|
|
165
|
+
" ",
|
|
166
|
+
o.label
|
|
167
|
+
] }, o.value)) });
|
|
168
|
+
}
|
|
169
|
+
case "tags":
|
|
170
|
+
return /* @__PURE__ */ jsx("input", { ...common, type: "text", placeholder: ui.placeholder || "One, two, three" });
|
|
171
|
+
default:
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function Form({
|
|
176
|
+
form,
|
|
177
|
+
showTitle,
|
|
178
|
+
submitLabel,
|
|
179
|
+
onSuccess,
|
|
180
|
+
successMessage,
|
|
181
|
+
redirectUrl,
|
|
182
|
+
className,
|
|
183
|
+
style,
|
|
184
|
+
editor
|
|
185
|
+
}) {
|
|
186
|
+
const client = usePlenaryClient();
|
|
187
|
+
const uid = useId();
|
|
188
|
+
const honeypotRef = useRef(null);
|
|
189
|
+
const [submitting, setSubmitting] = useState(false);
|
|
190
|
+
const [submitted, setSubmitted] = useState(false);
|
|
191
|
+
const [error, setError] = useState(null);
|
|
192
|
+
const definition = form?.definition;
|
|
193
|
+
if (!form?.form_key || !definition) {
|
|
194
|
+
return editor ? /* @__PURE__ */ jsx(FormPlaceholder, { className, style }) : null;
|
|
195
|
+
}
|
|
196
|
+
const fields = definition.fields ?? [];
|
|
197
|
+
const honeypot = !fields.some((f) => f.key === "website");
|
|
198
|
+
if (submitted) {
|
|
199
|
+
return /* @__PURE__ */ jsx("div", { className: className ? `plenary-form__success ${className}` : "plenary-form__success", style, role: "status", children: successMessage || "Thanks! Your submission has been received." });
|
|
200
|
+
}
|
|
201
|
+
async function handleSubmit(e) {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
if (editor || submitting) return;
|
|
204
|
+
const formEl = e.currentTarget;
|
|
205
|
+
if (!client) {
|
|
206
|
+
setError({ message: "This form is not connected \u2014 the page is missing a PlenaryProvider." });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
setError(null);
|
|
210
|
+
setSubmitting(true);
|
|
211
|
+
try {
|
|
212
|
+
await client.submitForm(form.form_key, collectValues(fields, formEl), {
|
|
213
|
+
...honeypot && honeypotRef.current ? { website: honeypotRef.current.value } : {},
|
|
214
|
+
...definition.ts ? { ts: definition.ts } : {}
|
|
215
|
+
});
|
|
216
|
+
if (onSuccess === "redirect" && redirectUrl) {
|
|
217
|
+
window.location.assign(redirectUrl);
|
|
218
|
+
} else {
|
|
219
|
+
setSubmitted(true);
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
if (err instanceof PlenaryError && typeof err.vars?.field === "string") {
|
|
223
|
+
setError({
|
|
224
|
+
field: err.vars.field,
|
|
225
|
+
message: FIELD_ERROR_MESSAGES[err.code] ?? "This value is invalid."
|
|
226
|
+
});
|
|
227
|
+
} else if (err instanceof PlenaryError && err.status === 429) {
|
|
228
|
+
setError({ message: "Too many submissions \u2014 please try again in a minute." });
|
|
229
|
+
} else {
|
|
230
|
+
setError({ message: "Something went wrong \u2014 please try again." });
|
|
231
|
+
}
|
|
232
|
+
} finally {
|
|
233
|
+
setSubmitting(false);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return /* @__PURE__ */ jsxs("form", { className: className ? `plenary-form ${className}` : "plenary-form", style, onSubmit: handleSubmit, noValidate: false, children: [
|
|
237
|
+
showTitle && /* @__PURE__ */ jsx("h2", { className: "plenary-form__title", children: definition.name }),
|
|
238
|
+
/* @__PURE__ */ jsxs(
|
|
239
|
+
"fieldset",
|
|
240
|
+
{
|
|
241
|
+
disabled: editor || submitting,
|
|
242
|
+
style: { border: 0, padding: 0, margin: 0, minInlineSize: 0 },
|
|
243
|
+
children: [
|
|
244
|
+
fields.map((f) => {
|
|
245
|
+
const id = `${uid}-${f.key}`;
|
|
246
|
+
if (UNSUPPORTED_TYPES.has(f.type)) {
|
|
247
|
+
return /* @__PURE__ */ jsx("div", { className: "plenary-form__field plenary-form__field--unsupported", children: /* @__PURE__ */ jsxs("p", { className: "plenary-form__notice", children: [
|
|
248
|
+
"\u201C",
|
|
249
|
+
f.name,
|
|
250
|
+
"\u201D uses a field type the Form block does not support."
|
|
251
|
+
] }) }, f.key);
|
|
252
|
+
}
|
|
253
|
+
const isCheckbox = f.type === "boolean";
|
|
254
|
+
const label = /* @__PURE__ */ jsxs("label", { className: "plenary-form__label", htmlFor: isCheckbox ? void 0 : id, children: [
|
|
255
|
+
isCheckbox && /* @__PURE__ */ jsx(FieldInput, { field: f, id }),
|
|
256
|
+
f.name,
|
|
257
|
+
f.required ? " *" : ""
|
|
258
|
+
] });
|
|
259
|
+
return /* @__PURE__ */ jsxs("div", { className: "plenary-form__field", children: [
|
|
260
|
+
label,
|
|
261
|
+
!isCheckbox && /* @__PURE__ */ jsx(FieldInput, { field: f, id }),
|
|
262
|
+
typeof f.ui?.help_text === "string" && f.ui.help_text !== "" && /* @__PURE__ */ jsx("p", { className: "plenary-form__help", children: f.ui.help_text }),
|
|
263
|
+
error?.field === f.key && /* @__PURE__ */ jsx("p", { className: "plenary-form__error", role: "alert", children: error.message })
|
|
264
|
+
] }, f.key);
|
|
265
|
+
}),
|
|
266
|
+
honeypot && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", left: "-9999px", width: 1, height: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsxs("label", { children: [
|
|
267
|
+
"Leave this field blank",
|
|
268
|
+
/* @__PURE__ */ jsx("input", { ref: honeypotRef, type: "text", name: "website", tabIndex: -1, autoComplete: "off" })
|
|
269
|
+
] }) }),
|
|
270
|
+
error && !error.field && /* @__PURE__ */ jsx("p", { className: "plenary-form__error", role: "alert", children: error.message }),
|
|
271
|
+
/* @__PURE__ */ jsx("div", { className: "plenary-form__actions", children: /* @__PURE__ */ jsx("button", { type: "submit", className: "plenary-form__submit", children: submitLabel || "Submit" }) })
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] });
|
|
276
|
+
}
|
|
277
|
+
export {
|
|
278
|
+
Form
|
|
279
|
+
};
|
|
280
|
+
//# sourceMappingURL=Form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Form.tsx"],"sourcesContent":["\"use client\";\n\nimport { useId, useRef, useState, type CSSProperties, type FormEvent, type ReactNode } from \"react\";\nimport { PlenaryError, type PublicFormField } from \"@plenary/client\";\nimport type { FormValue, ResolvedForm } from \"../form.js\";\nimport { usePlenaryClient } from \"../context.js\";\n\nexport interface FormProps {\n /** A resolved form value (the renderer resolves it before render). */\n form?: ResolvedForm | FormValue | null;\n showTitle?: boolean;\n submitLabel?: string;\n onSuccess?: \"message\" | \"redirect\";\n successMessage?: string;\n redirectUrl?: string;\n className?: string;\n style?: CSSProperties;\n /** True on editor-preview renders; enables the no-form placeholder. */\n editor?: boolean;\n}\n\n/** Field types the block renders no input for (v1) — a form needing them wants a custom implementation. */\nconst UNSUPPORTED_TYPES = new Set([\"object\", \"list\", \"json\"]);\n\n/** English messages for the per-field validation codes the submit endpoint returns. */\nconst FIELD_ERROR_MESSAGES: Record<string, string> = {\n entry_field_required: \"This field is required.\",\n entry_field_required_nonempty: \"This field is required.\",\n entry_field_value_required: \"This field is required.\",\n entry_field_value_taken: \"This value is already taken.\",\n};\n\n/** An empty-state stand-in so a form-less Form block is visible and selectable. */\nfunction FormPlaceholder({ className, style }: { className?: string; style?: CSSProperties }) {\n return (\n <div\n data-plenary-form-placeholder=\"\"\n className={className}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 8,\n minHeight: 120,\n border: \"1px dashed #94a3b8\",\n borderRadius: 4,\n background: \"#f1f5f9\",\n color: \"#94a3b8\",\n ...style,\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"2\" />\n <path d=\"M8 8h8M8 12h8M8 16h4\" />\n </svg>\n <span style={{ fontSize: 13 }}>Choose a form in the Properties tab</span>\n </div>\n );\n}\n\n/** Read the submitted values off the native form, typed per field definition. */\nfunction collectValues(fields: PublicFormField[], formEl: HTMLFormElement): Record<string, unknown> {\n const fd = new FormData(formEl);\n const data: Record<string, unknown> = {};\n for (const f of fields) {\n if (UNSUPPORTED_TYPES.has(f.type)) continue;\n switch (f.type) {\n case \"boolean\":\n data[f.key] = fd.get(f.key) === \"on\";\n break;\n case \"multi_select\": {\n const values = fd.getAll(f.key).map(String);\n if (values.length) data[f.key] = values;\n break;\n }\n case \"tags\": {\n const values = String(fd.get(f.key) ?? \"\")\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n if (values.length) data[f.key] = values;\n break;\n }\n case \"number\": {\n const raw = String(fd.get(f.key) ?? \"\").trim();\n if (raw !== \"\") data[f.key] = Number(raw);\n break;\n }\n default: {\n // Empty optional inputs are omitted rather than sent as \"\" — the\n // server format-validates present values (e.g. phone's E.164 rule).\n const raw = String(fd.get(f.key) ?? \"\");\n if (raw !== \"\") data[f.key] = raw;\n }\n }\n }\n return data;\n}\n\ninterface FieldInputProps {\n field: PublicFormField;\n id: string;\n}\n\n/** The input element(s) for one field, driven by its type and `ui`/`settings`. */\nfunction FieldInput({ field, id }: FieldInputProps) {\n const ui = field.ui ?? {};\n const validation = field.validation ?? {};\n const common = {\n id,\n name: field.key,\n required: field.required,\n className: \"plenary-form__input\",\n placeholder: (ui.placeholder as string) || undefined,\n };\n\n switch (field.type) {\n case \"text\":\n if (ui.control === \"textarea\") {\n return <textarea {...common} maxLength={validation.max_length as number | undefined} />;\n }\n return <input {...common} type=\"text\" maxLength={validation.max_length as number | undefined} />;\n case \"email\":\n return <input {...common} type=\"email\" />;\n case \"url\":\n return <input {...common} type=\"url\" />;\n case \"phone\":\n // The server enforces E.164; the pattern lets the browser say so first.\n return (\n <input\n {...common}\n type=\"tel\"\n pattern=\"\\+[1-9][0-9]{1,14}\"\n title=\"International format, e.g. +15551234567\"\n placeholder={(ui.placeholder as string) || \"+15551234567\"}\n />\n );\n case \"number\":\n return (\n <input\n {...common}\n type=\"number\"\n min={validation.min_val as number | undefined}\n max={validation.max_val as number | undefined}\n step={(validation.multiple_of as number | undefined) ?? \"any\"}\n />\n );\n case \"date\":\n return <input {...common} type=\"date\" />;\n case \"time\":\n return <input {...common} type=\"time\" />;\n case \"datetime\":\n return <input {...common} type=\"datetime-local\" />;\n case \"boolean\":\n return <input {...common} type=\"checkbox\" className=\"plenary-form__checkbox\" />;\n case \"select\": {\n const options = ((field.settings?.options as { value: string; label: string }[]) ?? []).filter(\n (o) => o && o.value !== undefined,\n );\n if (ui.control === \"radio\" || ui.control === \"segmented\") {\n return (\n <div className=\"plenary-form__options\" role=\"radiogroup\">\n {options.map((o, i) => (\n <label key={o.value} className=\"plenary-form__option\">\n <input\n type=\"radio\"\n name={field.key}\n value={o.value}\n required={field.required && i === 0}\n />{\" \"}\n {o.label}\n </label>\n ))}\n </div>\n );\n }\n return (\n <select {...common}>\n {!field.required && <option value=\"\"></option>}\n {options.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n );\n }\n case \"multi_select\": {\n const options = (field.settings?.options as { value: string; label: string }[]) ?? [];\n return (\n <div className=\"plenary-form__options\">\n {options.map((o) => (\n <label key={o.value} className=\"plenary-form__option\">\n <input type=\"checkbox\" name={field.key} value={o.value} /> {o.label}\n </label>\n ))}\n </div>\n );\n }\n case \"tags\":\n return <input {...common} type=\"text\" placeholder={(ui.placeholder as string) || \"One, two, three\"} />;\n default:\n return null;\n }\n}\n\n/**\n * Renders a Form Manager form from its resolved definition and submits entries\n * through the provided client (see `PlenaryProvider`). On success it either\n * swaps itself for `successMessage` or navigates to `redirectUrl`.\n *\n * Editor previews render the real fields inert (a disabled fieldset), so the\n * block is selectable on the canvas without being fillable; the bridge also\n * cancels any submit while the console drives the iframe. Without a chosen\n * form it renders a placeholder in the editor and nothing on a published page.\n *\n * The hidden `website` input is the server's reserved honeypot key and the\n * `ts` token (when the definition carries one) is echoed back — both are\n * ignored by servers without anti-spam enabled. The honeypot is skipped in the\n * rare case the form itself defines a `website` field.\n */\nexport function Form({\n form,\n showTitle,\n submitLabel,\n onSuccess,\n successMessage,\n redirectUrl,\n className,\n style,\n editor,\n}: FormProps) {\n const client = usePlenaryClient();\n const uid = useId();\n const honeypotRef = useRef<HTMLInputElement>(null);\n const [submitting, setSubmitting] = useState(false);\n const [submitted, setSubmitted] = useState(false);\n const [error, setError] = useState<{ field?: string; message: string } | null>(null);\n\n const definition = form?.definition;\n if (!form?.form_key || !definition) {\n return editor ? <FormPlaceholder className={className} style={style} /> : null;\n }\n\n const fields = definition.fields ?? [];\n const honeypot = !fields.some((f) => f.key === \"website\");\n\n if (submitted) {\n return (\n <div className={className ? `plenary-form__success ${className}` : \"plenary-form__success\"} style={style} role=\"status\">\n {successMessage || \"Thanks! Your submission has been received.\"}\n </div>\n );\n }\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>) {\n e.preventDefault();\n if (editor || submitting) return;\n const formEl = e.currentTarget;\n if (!client) {\n setError({ message: \"This form is not connected — the page is missing a PlenaryProvider.\" });\n return;\n }\n setError(null);\n setSubmitting(true);\n try {\n await client.submitForm(form!.form_key!, collectValues(fields, formEl), {\n ...(honeypot && honeypotRef.current ? { website: honeypotRef.current.value } : {}),\n ...(definition!.ts ? { ts: definition!.ts } : {}),\n });\n if (onSuccess === \"redirect\" && redirectUrl) {\n window.location.assign(redirectUrl);\n } else {\n setSubmitted(true);\n }\n } catch (err) {\n if (err instanceof PlenaryError && typeof err.vars?.field === \"string\") {\n setError({\n field: err.vars.field,\n message: FIELD_ERROR_MESSAGES[err.code] ?? \"This value is invalid.\",\n });\n } else if (err instanceof PlenaryError && err.status === 429) {\n setError({ message: \"Too many submissions — please try again in a minute.\" });\n } else {\n setError({ message: \"Something went wrong — please try again.\" });\n }\n } finally {\n setSubmitting(false);\n }\n }\n\n return (\n <form className={className ? `plenary-form ${className}` : \"plenary-form\"} style={style} onSubmit={handleSubmit} noValidate={false}>\n {showTitle && <h2 className=\"plenary-form__title\">{definition.name}</h2>}\n <fieldset\n disabled={editor || submitting}\n style={{ border: 0, padding: 0, margin: 0, minInlineSize: 0 }}\n >\n {fields.map((f) => {\n const id = `${uid}-${f.key}`;\n if (UNSUPPORTED_TYPES.has(f.type)) {\n return (\n <div key={f.key} className=\"plenary-form__field plenary-form__field--unsupported\">\n <p className=\"plenary-form__notice\">\n “{f.name}” uses a field type the Form block does not support.\n </p>\n </div>\n );\n }\n const isCheckbox = f.type === \"boolean\";\n const label = (\n <label className=\"plenary-form__label\" htmlFor={isCheckbox ? undefined : id}>\n {isCheckbox && <FieldInput field={f} id={id} />}\n {f.name}\n {f.required ? \" *\" : \"\"}\n </label>\n );\n return (\n <div key={f.key} className=\"plenary-form__field\">\n {label}\n {!isCheckbox && <FieldInput field={f} id={id} />}\n {typeof f.ui?.help_text === \"string\" && f.ui.help_text !== \"\" && (\n <p className=\"plenary-form__help\">{f.ui.help_text as ReactNode}</p>\n )}\n {error?.field === f.key && (\n <p className=\"plenary-form__error\" role=\"alert\">\n {error.message}\n </p>\n )}\n </div>\n );\n })}\n {honeypot && (\n <div aria-hidden=\"true\" style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1, overflow: \"hidden\" }}>\n <label>\n Leave this field blank\n <input ref={honeypotRef} type=\"text\" name=\"website\" tabIndex={-1} autoComplete=\"off\" />\n </label>\n </div>\n )}\n {error && !error.field && (\n <p className=\"plenary-form__error\" role=\"alert\">\n {error.message}\n </p>\n )}\n <div className=\"plenary-form__actions\">\n <button type=\"submit\" className=\"plenary-form__submit\">\n {submitLabel || \"Submit\"}\n </button>\n </div>\n </fieldset>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import { ResolvedMedia, MediaValue } from '../media.js';
|
|
4
|
+
import '@plenary/client';
|
|
5
|
+
|
|
6
|
+
interface ImageProps {
|
|
7
|
+
/** A resolved media value (the renderer resolves it before render). */
|
|
8
|
+
media?: ResolvedMedia | MediaValue | null;
|
|
9
|
+
alt?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: CSSProperties;
|
|
12
|
+
/** True on editor-preview renders; enables the no-media placeholder. */
|
|
13
|
+
editor?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* An `img` sourced from a resolved media prop. The block's one-off variant
|
|
17
|
+
* (server-hydrated `variant_url`) wins over the original when present. Without
|
|
18
|
+
* any URL it renders nothing on a published page — but in the editor preview it
|
|
19
|
+
* renders a placeholder, so an Image block with no media assigned can still be
|
|
20
|
+
* seen and selected. A reference whose asset was deleted gets its own
|
|
21
|
+
* placeholder, so the editor can tell it apart from an unset one.
|
|
22
|
+
*/
|
|
23
|
+
declare function Image({ media, alt, className, style, editor }: ImageProps): react.JSX.Element | null;
|
|
24
|
+
|
|
25
|
+
export { Image, type ImageProps };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
function ImagePlaceholder({ className, style }) {
|
|
3
|
+
return /* @__PURE__ */ jsx(
|
|
4
|
+
"div",
|
|
5
|
+
{
|
|
6
|
+
"data-plenary-image-placeholder": "",
|
|
7
|
+
className,
|
|
8
|
+
style: {
|
|
9
|
+
display: "flex",
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
justifyContent: "center",
|
|
12
|
+
minHeight: 120,
|
|
13
|
+
border: "1px dashed #94a3b8",
|
|
14
|
+
borderRadius: 4,
|
|
15
|
+
background: "#f1f5f9",
|
|
16
|
+
color: "#94a3b8",
|
|
17
|
+
...style
|
|
18
|
+
},
|
|
19
|
+
children: /* @__PURE__ */ jsxs(
|
|
20
|
+
"svg",
|
|
21
|
+
{
|
|
22
|
+
width: "32",
|
|
23
|
+
height: "32",
|
|
24
|
+
viewBox: "0 0 24 24",
|
|
25
|
+
fill: "none",
|
|
26
|
+
stroke: "currentColor",
|
|
27
|
+
strokeWidth: "1.5",
|
|
28
|
+
strokeLinecap: "round",
|
|
29
|
+
strokeLinejoin: "round",
|
|
30
|
+
"aria-hidden": "true",
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
33
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
34
|
+
/* @__PURE__ */ jsx("path", { d: "m21 15-5-5L5 21" })
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function MissingImagePlaceholder({
|
|
42
|
+
className,
|
|
43
|
+
style
|
|
44
|
+
}) {
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
"div",
|
|
47
|
+
{
|
|
48
|
+
"data-plenary-image-missing": "",
|
|
49
|
+
className,
|
|
50
|
+
title: "Image not found \u2014 it may have been deleted from the Media Library",
|
|
51
|
+
style: {
|
|
52
|
+
display: "flex",
|
|
53
|
+
flexDirection: "column",
|
|
54
|
+
alignItems: "center",
|
|
55
|
+
justifyContent: "center",
|
|
56
|
+
gap: 6,
|
|
57
|
+
minHeight: 120,
|
|
58
|
+
padding: 8,
|
|
59
|
+
border: "1px dashed #f87171",
|
|
60
|
+
borderRadius: 4,
|
|
61
|
+
background: "#fef2f2",
|
|
62
|
+
color: "#b91c1c",
|
|
63
|
+
font: "500 12px/1.4 system-ui, sans-serif",
|
|
64
|
+
textAlign: "center",
|
|
65
|
+
...style
|
|
66
|
+
},
|
|
67
|
+
children: [
|
|
68
|
+
/* @__PURE__ */ jsxs(
|
|
69
|
+
"svg",
|
|
70
|
+
{
|
|
71
|
+
width: "32",
|
|
72
|
+
height: "32",
|
|
73
|
+
viewBox: "0 0 24 24",
|
|
74
|
+
fill: "none",
|
|
75
|
+
stroke: "currentColor",
|
|
76
|
+
strokeWidth: "1.5",
|
|
77
|
+
strokeLinecap: "round",
|
|
78
|
+
strokeLinejoin: "round",
|
|
79
|
+
"aria-hidden": "true",
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
82
|
+
/* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
83
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15l-5-5L9.5 16.5" }),
|
|
84
|
+
/* @__PURE__ */ jsx("path", { d: "m3 3 18 18" })
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
/* @__PURE__ */ jsx("span", { children: "Image not found" })
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
function Image({ media, alt, className, style, editor }) {
|
|
94
|
+
if (media?.missing) {
|
|
95
|
+
return editor ? /* @__PURE__ */ jsx(MissingImagePlaceholder, { className, style }) : null;
|
|
96
|
+
}
|
|
97
|
+
const url = media?.variant_url || media?.url;
|
|
98
|
+
if (!url) {
|
|
99
|
+
return editor ? /* @__PURE__ */ jsx(ImagePlaceholder, { className, style }) : null;
|
|
100
|
+
}
|
|
101
|
+
return /* @__PURE__ */ jsx(
|
|
102
|
+
"img",
|
|
103
|
+
{
|
|
104
|
+
src: url,
|
|
105
|
+
alt: alt ?? media?.alt ?? "",
|
|
106
|
+
width: media?.width,
|
|
107
|
+
height: media?.height,
|
|
108
|
+
className,
|
|
109
|
+
style
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
Image
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=Image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Image.tsx"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { MediaValue, ResolvedMedia } from \"../media.js\";\n\nexport interface ImageProps {\n /** A resolved media value (the renderer resolves it before render). */\n media?: ResolvedMedia | MediaValue | null;\n alt?: string;\n className?: string;\n style?: CSSProperties;\n /** True on editor-preview renders; enables the no-media placeholder. */\n editor?: boolean;\n}\n\n/** An empty-state stand-in so a media-less Image block is visible and selectable. */\nfunction ImagePlaceholder({ className, style }: { className?: string; style?: CSSProperties }) {\n return (\n <div\n data-plenary-image-placeholder=\"\"\n className={className}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n minHeight: 120,\n border: \"1px dashed #94a3b8\",\n borderRadius: 4,\n background: \"#f1f5f9\",\n color: \"#94a3b8\",\n ...style,\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <path d=\"m21 15-5-5L5 21\" />\n </svg>\n </div>\n );\n}\n\n/**\n * The stand-in for a dangling reference — an asset deleted from the Media\n * Library while blocks still point at it. Deliberately distinct from the\n * empty-state placeholder: \"this image is gone\" and \"no image chosen yet\" are\n * different problems, and only one of them is the editor's own doing.\n */\nfunction MissingImagePlaceholder({\n className,\n style,\n}: {\n className?: string;\n style?: CSSProperties;\n}) {\n return (\n <div\n data-plenary-image-missing=\"\"\n className={className}\n title=\"Image not found — it may have been deleted from the Media Library\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 6,\n minHeight: 120,\n padding: 8,\n border: \"1px dashed #f87171\",\n borderRadius: 4,\n background: \"#fef2f2\",\n color: \"#b91c1c\",\n font: \"500 12px/1.4 system-ui, sans-serif\",\n textAlign: \"center\",\n ...style,\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <path d=\"M21 15l-5-5L9.5 16.5\" />\n <path d=\"m3 3 18 18\" />\n </svg>\n <span>Image not found</span>\n </div>\n );\n}\n\n/**\n * An `img` sourced from a resolved media prop. The block's one-off variant\n * (server-hydrated `variant_url`) wins over the original when present. Without\n * any URL it renders nothing on a published page — but in the editor preview it\n * renders a placeholder, so an Image block with no media assigned can still be\n * seen and selected. A reference whose asset was deleted gets its own\n * placeholder, so the editor can tell it apart from an unset one.\n */\nexport function Image({ media, alt, className, style, editor }: ImageProps) {\n if (media?.missing) {\n return editor ? <MissingImagePlaceholder className={className} style={style} /> : null;\n }\n\n const url = media?.variant_url || media?.url;\n if (!url) {\n return editor ? <ImagePlaceholder className={className} style={style} /> : null;\n }\n\n return (\n <img\n src={url}\n alt={alt ?? media?.alt ?? \"\"}\n width={media?.width}\n height={media?.height}\n className={className}\n style={style}\n />\n );\n}\n"],"mappings":"AA+BM,SAWE,KAXF;AAjBN,SAAS,iBAAiB,EAAE,WAAW,MAAM,GAAkD;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,kCAA+B;AAAA,MAC/B;AAAA,MACA,OAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,GAAG;AAAA,MACL;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAO;AAAA,UACP,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,QAAO;AAAA,UACP,aAAY;AAAA,UACZ,eAAc;AAAA,UACd,gBAAe;AAAA,UACf,eAAY;AAAA,UAEZ;AAAA,gCAAC,UAAK,GAAE,KAAI,GAAE,KAAI,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI;AAAA,YAChD,oBAAC,YAAO,IAAG,OAAM,IAAG,OAAM,GAAE,OAAM;AAAA,YAClC,oBAAC,UAAK,GAAE,mBAAkB;AAAA;AAAA;AAAA,MAC5B;AAAA;AAAA,EACF;AAEJ;AAQA,SAAS,wBAAwB;AAAA,EAC/B;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,8BAA2B;AAAA,MAC3B;AAAA,MACA,OAAM;AAAA,MACN,OAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,KAAK;AAAA,QACL,WAAW;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,MAAM;AAAA,QACN,WAAW;AAAA,QACX,GAAG;AAAA,MACL;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAM;AAAA,YACN,QAAO;AAAA,YACP,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,eAAc;AAAA,YACd,gBAAe;AAAA,YACf,eAAY;AAAA,YAEZ;AAAA,kCAAC,UAAK,GAAE,KAAI,GAAE,KAAI,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI;AAAA,cAChD,oBAAC,YAAO,IAAG,OAAM,IAAG,OAAM,GAAE,OAAM;AAAA,cAClC,oBAAC,UAAK,GAAE,wBAAuB;AAAA,cAC/B,oBAAC,UAAK,GAAE,cAAa;AAAA;AAAA;AAAA,QACvB;AAAA,QACA,oBAAC,UAAK,6BAAe;AAAA;AAAA;AAAA,EACvB;AAEJ;AAUO,SAAS,MAAM,EAAE,OAAO,KAAK,WAAW,OAAO,OAAO,GAAe;AAC1E,MAAI,OAAO,SAAS;AAClB,WAAO,SAAS,oBAAC,2BAAwB,WAAsB,OAAc,IAAK;AAAA,EACpF;AAEA,QAAM,MAAM,OAAO,eAAe,OAAO;AACzC,MAAI,CAAC,KAAK;AACR,WAAO,SAAS,oBAAC,oBAAiB,WAAsB,OAAc,IAAK;AAAA,EAC7E;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,KAAK,OAAO,OAAO,OAAO;AAAA,MAC1B,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface PlaceholderProps {
|
|
4
|
+
component: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Dev-visible stand-in for a dom node whose component the deployed bundle does
|
|
8
|
+
* not have registered. Keeps an unknown component from crashing the render.
|
|
9
|
+
*/
|
|
10
|
+
declare function Placeholder({ component }: PlaceholderProps): react.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { Placeholder, type PlaceholderProps };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
function Placeholder({ component }) {
|
|
3
|
+
return /* @__PURE__ */ jsxs(
|
|
4
|
+
"div",
|
|
5
|
+
{
|
|
6
|
+
"data-plenary-unknown": component,
|
|
7
|
+
style: {
|
|
8
|
+
padding: "8px 12px",
|
|
9
|
+
border: "1px dashed #d00",
|
|
10
|
+
borderRadius: 4,
|
|
11
|
+
color: "#d00",
|
|
12
|
+
font: "12px/1.4 monospace"
|
|
13
|
+
},
|
|
14
|
+
children: [
|
|
15
|
+
"Unknown component: ",
|
|
16
|
+
/* @__PURE__ */ jsx("strong", { children: component })
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
Placeholder
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=Placeholder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Placeholder.tsx"],"sourcesContent":["export interface PlaceholderProps {\n component: string;\n}\n\n/**\n * Dev-visible stand-in for a dom node whose component the deployed bundle does\n * not have registered. Keeps an unknown component from crashing the render.\n */\nexport function Placeholder({ component }: PlaceholderProps) {\n return (\n <div\n data-plenary-unknown={component}\n style={{\n padding: \"8px 12px\",\n border: \"1px dashed #d00\",\n borderRadius: 4,\n color: \"#d00\",\n font: \"12px/1.4 monospace\",\n }}\n >\n Unknown component: <strong>{component}</strong>\n </div>\n );\n}\n"],"mappings":"AAUI,SAUqB,KAVrB;AAFG,SAAS,YAAY,EAAE,UAAU,GAAqB;AAC3D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,wBAAsB;AAAA,MACtB,OAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACD;AAAA;AAAA,QACoB,oBAAC,YAAQ,qBAAU;AAAA;AAAA;AAAA,EACxC;AAEJ;","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface SectionProps {
|
|
5
|
+
/** Number of columns (1–6). Drives how many index-keyed slots render. */
|
|
6
|
+
columns?: number;
|
|
7
|
+
gap?: number | string;
|
|
8
|
+
className?: string;
|
|
9
|
+
style?: CSSProperties;
|
|
10
|
+
/** Index-keyed column slots: "0", "1", … delivered by the renderer. */
|
|
11
|
+
slots?: Record<string, ReactNode>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The columns primitive. Renders `columns` flex cells, each showing its
|
|
15
|
+
* index-keyed slot. Reducing `columns` is a builder concern (orphaned column
|
|
16
|
+
* content); the renderer just renders `columns` cells.
|
|
17
|
+
*/
|
|
18
|
+
declare function Section({ columns, gap, className, style, slots }: SectionProps): react.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { Section, type SectionProps };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
function Section({ columns = 1, gap = 16, className, style, slots }) {
|
|
3
|
+
const count = Math.max(1, Math.min(6, Math.floor(columns)));
|
|
4
|
+
const cells = [];
|
|
5
|
+
for (let i = 0; i < count; i++) {
|
|
6
|
+
cells.push(
|
|
7
|
+
/* @__PURE__ */ jsx("div", { "data-plenary-column": i, style: { flex: "1 1 0", minWidth: 0 }, children: slots?.[String(i)] ?? null }, i)
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return /* @__PURE__ */ jsx("div", { className, "data-plenary-columns": count, style: { display: "flex", gap, ...style }, children: cells });
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
Section
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=Section.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Section.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from \"react\";\n\nexport interface SectionProps {\n /** Number of columns (1–6). Drives how many index-keyed slots render. */\n columns?: number;\n gap?: number | string;\n className?: string;\n style?: CSSProperties;\n /** Index-keyed column slots: \"0\", \"1\", … delivered by the renderer. */\n slots?: Record<string, ReactNode>;\n}\n\n/**\n * The columns primitive. Renders `columns` flex cells, each showing its\n * index-keyed slot. Reducing `columns` is a builder concern (orphaned column\n * content); the renderer just renders `columns` cells.\n */\nexport function Section({ columns = 1, gap = 16, className, style, slots }: SectionProps) {\n const count = Math.max(1, Math.min(6, Math.floor(columns)));\n const cells: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n cells.push(\n <div key={i} data-plenary-column={i} style={{ flex: \"1 1 0\", minWidth: 0 }}>\n {slots?.[String(i)] ?? null}\n </div>,\n );\n }\n return (\n <div className={className} data-plenary-columns={count} style={{ display: \"flex\", gap, ...style }}>\n {cells}\n </div>\n );\n}\n"],"mappings":"AAsBM;AALC,SAAS,QAAQ,EAAE,UAAU,GAAG,MAAM,IAAI,WAAW,OAAO,MAAM,GAAiB;AACxF,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC;AAC1D,QAAM,QAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM;AAAA,MACJ,oBAAC,SAAY,uBAAqB,GAAG,OAAO,EAAE,MAAM,SAAS,UAAU,EAAE,GACtE,kBAAQ,OAAO,CAAC,CAAC,KAAK,QADf,CAEV;AAAA,IACF;AAAA,EACF;AACA,SACE,oBAAC,SAAI,WAAsB,wBAAsB,OAAO,OAAO,EAAE,SAAS,QAAQ,KAAK,GAAG,MAAM,GAC7F,iBACH;AAEJ;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ElementType, CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
interface TextProps {
|
|
5
|
+
/** Plain-text content (v1). Rich text is a later enhancement. */
|
|
6
|
+
content?: string;
|
|
7
|
+
/** Element/format: `p`, `h1`–`h6`, `span`, `code`, … Defaults to `p`. */
|
|
8
|
+
as?: ElementType;
|
|
9
|
+
className?: string;
|
|
10
|
+
style?: CSSProperties;
|
|
11
|
+
}
|
|
12
|
+
/** A text leaf rendered as the element named by `as`. */
|
|
13
|
+
declare function Text({ content, as, className, style }: TextProps): react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
14
|
+
|
|
15
|
+
export { Text, type TextProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Text.tsx"],"sourcesContent":["import { createElement, type CSSProperties, type ElementType } from \"react\";\n\nexport interface TextProps {\n /** Plain-text content (v1). Rich text is a later enhancement. */\n content?: string;\n /** Element/format: `p`, `h1`–`h6`, `span`, `code`, … Defaults to `p`. */\n as?: ElementType;\n className?: string;\n style?: CSSProperties;\n}\n\n/** A text leaf rendered as the element named by `as`. */\nexport function Text({ content = \"\", as = \"p\", className, style }: TextProps) {\n return createElement(as, { className, style }, content);\n}\n"],"mappings":"AAAA,SAAS,qBAA2D;AAY7D,SAAS,KAAK,EAAE,UAAU,IAAI,KAAK,KAAK,WAAW,MAAM,GAAc;AAC5E,SAAO,cAAc,IAAI,EAAE,WAAW,MAAM,GAAG,OAAO;AACxD;","names":[]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import { ResolvedMedia, MediaValue } from '../media.js';
|
|
4
|
+
import '@plenary/client';
|
|
5
|
+
|
|
6
|
+
interface VideoProps {
|
|
7
|
+
/** A resolved media value (the renderer resolves it before render). */
|
|
8
|
+
media?: ResolvedMedia | MediaValue | null;
|
|
9
|
+
/** Optional poster image, an independent media pick. */
|
|
10
|
+
poster?: ResolvedMedia | MediaValue | null;
|
|
11
|
+
controls?: boolean;
|
|
12
|
+
autoplay?: boolean;
|
|
13
|
+
loop?: boolean;
|
|
14
|
+
muted?: boolean;
|
|
15
|
+
playsinline?: boolean;
|
|
16
|
+
preload?: "none" | "metadata" | "auto";
|
|
17
|
+
className?: string;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
/** True on editor-preview renders; enables the no-media placeholder. */
|
|
20
|
+
editor?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* An HTML5 `video` sourced from a resolved media prop; the file is served
|
|
24
|
+
* as uploaded (no transcoding). Without a URL it renders nothing on a
|
|
25
|
+
* published page — but in the editor preview it renders a placeholder, so a
|
|
26
|
+
* Video block with no media assigned can still be seen and selected. A
|
|
27
|
+
* reference whose asset was deleted gets its own placeholder, so the editor can
|
|
28
|
+
* tell it apart from an unset one.
|
|
29
|
+
*
|
|
30
|
+
* A deleted *poster* needs no such handling: its URL is dropped with the rest
|
|
31
|
+
* of the missing value, so the player simply renders without one.
|
|
32
|
+
*
|
|
33
|
+
* Editor previews render the player inert: the native controls are hidden so
|
|
34
|
+
* a canvas click selects the block instead of toggling playback (a video
|
|
35
|
+
* without controls ignores clicks, and the events bubble to the bridge's
|
|
36
|
+
* selection handler — pointer-events must stay on, or the node would be
|
|
37
|
+
* unhoverable since its annotation wrapper has no box of its own), and
|
|
38
|
+
* autoplay is suppressed so nothing plays while editing.
|
|
39
|
+
*
|
|
40
|
+
* A replaced element renders at the file's intrinsic pixel size by default,
|
|
41
|
+
* which overflows any smaller container (e.g. a section column) — so the
|
|
42
|
+
* responsive default `max-width: 100%; height: auto` is baked in. The Style
|
|
43
|
+
* tab still wins: its compiled sheet applies with `!important`, and explicit
|
|
44
|
+
* inline `style` values override the defaults via merge order.
|
|
45
|
+
*/
|
|
46
|
+
declare function Video({ media, poster, controls, autoplay, loop, muted, playsinline, preload, className, style, editor, }: VideoProps): react.JSX.Element | null;
|
|
47
|
+
|
|
48
|
+
export { Video, type VideoProps };
|