@sevenfold/setto-client 0.5.0 → 0.6.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/README.md +9 -14
- package/dist/SettoAnimation.d.ts +22 -0
- package/dist/SettoForm.d.ts +18 -0
- package/dist/SettoVideo.d.ts +16 -0
- package/dist/block-tree/SettoBlockTree.d.ts +24 -0
- package/dist/block-tree/extract-keys.d.ts +30 -0
- package/dist/block-tree/registry.d.ts +24 -0
- package/dist/block-tree/schema.d.ts +120 -0
- package/dist/block-tree/validate.d.ts +28 -0
- package/dist/index.d.ts +13 -0
- package/dist/lib/api.d.ts +2 -0
- package/dist/setto-client.js +730 -10
- package/dist/setto-client.js.map +1 -1
- package/package.json +5 -3
package/dist/setto-client.js
CHANGED
|
@@ -20805,6 +20805,18 @@ function createApi(args) {
|
|
|
20805
20805
|
}
|
|
20806
20806
|
);
|
|
20807
20807
|
if (!res.ok) throw await asError(res, "cancelDeployment failed");
|
|
20808
|
+
},
|
|
20809
|
+
/** Request a password-reset email (link goes via setto-server, then site /setto). */
|
|
20810
|
+
async requestPasswordReset(siteId, email) {
|
|
20811
|
+
const res = await fetch(
|
|
20812
|
+
`${apiUrl}/sites/${encodeURIComponent(siteId)}/auth/password-reset`,
|
|
20813
|
+
{
|
|
20814
|
+
method: "POST",
|
|
20815
|
+
headers: { "Content-Type": "application/json" },
|
|
20816
|
+
body: JSON.stringify({ email })
|
|
20817
|
+
}
|
|
20818
|
+
);
|
|
20819
|
+
if (!res.ok) throw await asError(res, "requestPasswordReset failed");
|
|
20808
20820
|
}
|
|
20809
20821
|
};
|
|
20810
20822
|
}
|
|
@@ -21199,9 +21211,6 @@ function isAdminRoute() {
|
|
|
21199
21211
|
function isAdminDeploymentView() {
|
|
21200
21212
|
return isAdminRoute() && new URLSearchParams(window.location.search).has("deployment");
|
|
21201
21213
|
}
|
|
21202
|
-
function adminRedirectUrl() {
|
|
21203
|
-
return `${window.location.origin}${SETTO_BASE}`;
|
|
21204
|
-
}
|
|
21205
21214
|
const FAB_SIZE = 52;
|
|
21206
21215
|
const FAB_INSET = 16;
|
|
21207
21216
|
const STYLE_ID$1 = "setto-edit-layout";
|
|
@@ -23786,6 +23795,236 @@ const removeBtnStyle = {
|
|
|
23786
23795
|
boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
|
|
23787
23796
|
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
23788
23797
|
};
|
|
23798
|
+
function SettoForm({
|
|
23799
|
+
formId,
|
|
23800
|
+
fields,
|
|
23801
|
+
endpoint,
|
|
23802
|
+
endpointKey,
|
|
23803
|
+
className
|
|
23804
|
+
}) {
|
|
23805
|
+
const { t } = useTranslation();
|
|
23806
|
+
const [status, setStatus] = useState("idle");
|
|
23807
|
+
const [values, setValues] = useState(
|
|
23808
|
+
() => Object.fromEntries(fields.map((f) => [f.name, ""]))
|
|
23809
|
+
);
|
|
23810
|
+
const setField = useCallback(
|
|
23811
|
+
(name, value) => setValues((prev) => ({ ...prev, [name]: value })),
|
|
23812
|
+
[]
|
|
23813
|
+
);
|
|
23814
|
+
const resolveEndpoint = () => {
|
|
23815
|
+
if (endpoint) return endpoint;
|
|
23816
|
+
if (endpointKey) {
|
|
23817
|
+
const resolved = t(endpointKey, { defaultValue: "" });
|
|
23818
|
+
return resolved && resolved !== endpointKey ? resolved : null;
|
|
23819
|
+
}
|
|
23820
|
+
return null;
|
|
23821
|
+
};
|
|
23822
|
+
const onSubmit = async (e) => {
|
|
23823
|
+
e.preventDefault();
|
|
23824
|
+
const url = resolveEndpoint();
|
|
23825
|
+
if (!url) {
|
|
23826
|
+
setStatus("error");
|
|
23827
|
+
return;
|
|
23828
|
+
}
|
|
23829
|
+
setStatus("submitting");
|
|
23830
|
+
try {
|
|
23831
|
+
const res = await fetch(url, {
|
|
23832
|
+
method: "POST",
|
|
23833
|
+
headers: { "Content-Type": "application/json" },
|
|
23834
|
+
body: JSON.stringify(values)
|
|
23835
|
+
});
|
|
23836
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
23837
|
+
setStatus("success");
|
|
23838
|
+
setValues(
|
|
23839
|
+
(prev) => Object.fromEntries(Object.keys(prev).map((k) => [k, ""]))
|
|
23840
|
+
);
|
|
23841
|
+
} catch {
|
|
23842
|
+
setStatus("error");
|
|
23843
|
+
}
|
|
23844
|
+
};
|
|
23845
|
+
if (status === "success") {
|
|
23846
|
+
return /* @__PURE__ */ jsx("div", { className, "data-setto-form-status": "success", children: /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.success` }) }) });
|
|
23847
|
+
}
|
|
23848
|
+
return /* @__PURE__ */ jsxs("form", { className, onSubmit, "data-setto-form": formId, noValidate: true, children: [
|
|
23849
|
+
fields.map((field) => /* @__PURE__ */ jsxs("label", { "data-setto-form-field": field.name, children: [
|
|
23850
|
+
/* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(T, { k: field.labelKey }) }),
|
|
23851
|
+
field.type === "textarea" ? /* @__PURE__ */ jsx(
|
|
23852
|
+
"textarea",
|
|
23853
|
+
{
|
|
23854
|
+
name: field.name,
|
|
23855
|
+
required: field.required,
|
|
23856
|
+
value: values[field.name] ?? "",
|
|
23857
|
+
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : void 0,
|
|
23858
|
+
onChange: (e) => setField(field.name, e.target.value)
|
|
23859
|
+
}
|
|
23860
|
+
) : /* @__PURE__ */ jsx(
|
|
23861
|
+
"input",
|
|
23862
|
+
{
|
|
23863
|
+
type: field.type,
|
|
23864
|
+
name: field.name,
|
|
23865
|
+
required: field.required,
|
|
23866
|
+
value: values[field.name] ?? "",
|
|
23867
|
+
placeholder: field.placeholderKey ? t(field.placeholderKey, { defaultValue: "" }) : void 0,
|
|
23868
|
+
onChange: (e) => setField(field.name, e.target.value)
|
|
23869
|
+
}
|
|
23870
|
+
)
|
|
23871
|
+
] }, field.name)),
|
|
23872
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: status === "submitting", children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.submit` }) }),
|
|
23873
|
+
status === "error" ? /* @__PURE__ */ jsx("p", { "data-setto-form-status": "error", role: "alert", children: /* @__PURE__ */ jsx(T, { k: `form.${formId}.error` }) }) : null
|
|
23874
|
+
] });
|
|
23875
|
+
}
|
|
23876
|
+
function SettoVideo({ srcKey, posterKey, title, className }) {
|
|
23877
|
+
const { t, i18n } = useTranslation();
|
|
23878
|
+
const { store } = useSetto();
|
|
23879
|
+
const src = store ? store.get(srcKey, i18n.language) : t(srcKey, { defaultValue: "" });
|
|
23880
|
+
const poster = posterKey ? store ? store.get(posterKey, i18n.language) : t(posterKey, { defaultValue: "" }) : void 0;
|
|
23881
|
+
if (!src) {
|
|
23882
|
+
return /* @__PURE__ */ jsx(
|
|
23883
|
+
"div",
|
|
23884
|
+
{
|
|
23885
|
+
className,
|
|
23886
|
+
"data-setto-video": srcKey,
|
|
23887
|
+
"data-setto-video-empty": "1"
|
|
23888
|
+
}
|
|
23889
|
+
);
|
|
23890
|
+
}
|
|
23891
|
+
const embed = toEmbedUrl(src);
|
|
23892
|
+
if (embed) {
|
|
23893
|
+
return /* @__PURE__ */ jsx(
|
|
23894
|
+
"iframe",
|
|
23895
|
+
{
|
|
23896
|
+
className,
|
|
23897
|
+
src: embed,
|
|
23898
|
+
title: title ?? "Video",
|
|
23899
|
+
loading: "lazy",
|
|
23900
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
23901
|
+
allowFullScreen: true,
|
|
23902
|
+
"data-setto-video": srcKey
|
|
23903
|
+
}
|
|
23904
|
+
);
|
|
23905
|
+
}
|
|
23906
|
+
return /* @__PURE__ */ jsx(
|
|
23907
|
+
"video",
|
|
23908
|
+
{
|
|
23909
|
+
className,
|
|
23910
|
+
src,
|
|
23911
|
+
poster,
|
|
23912
|
+
controls: true,
|
|
23913
|
+
preload: "metadata",
|
|
23914
|
+
"data-setto-video": srcKey
|
|
23915
|
+
}
|
|
23916
|
+
);
|
|
23917
|
+
}
|
|
23918
|
+
function toEmbedUrl(url) {
|
|
23919
|
+
try {
|
|
23920
|
+
const u = new URL(url);
|
|
23921
|
+
if (u.hostname === "youtu.be") {
|
|
23922
|
+
return `https://www.youtube.com/embed/${u.pathname.slice(1)}`;
|
|
23923
|
+
}
|
|
23924
|
+
if (u.hostname.endsWith("youtube.com")) {
|
|
23925
|
+
const v = u.searchParams.get("v");
|
|
23926
|
+
if (v) return `https://www.youtube.com/embed/${v}`;
|
|
23927
|
+
if (u.pathname.startsWith("/embed/")) return url;
|
|
23928
|
+
}
|
|
23929
|
+
if (u.hostname.endsWith("vimeo.com")) {
|
|
23930
|
+
const id = u.pathname.split("/").filter(Boolean).pop();
|
|
23931
|
+
if (id && /^\d+$/.test(id)) return `https://player.vimeo.com/video/${id}`;
|
|
23932
|
+
}
|
|
23933
|
+
return null;
|
|
23934
|
+
} catch {
|
|
23935
|
+
return null;
|
|
23936
|
+
}
|
|
23937
|
+
}
|
|
23938
|
+
const DEFAULT_DURATION = {
|
|
23939
|
+
fadeIn: 600,
|
|
23940
|
+
slideUp: 700,
|
|
23941
|
+
stagger: 700,
|
|
23942
|
+
scrollReveal: 700
|
|
23943
|
+
};
|
|
23944
|
+
function SettoAnimation({
|
|
23945
|
+
preset,
|
|
23946
|
+
delay = 0,
|
|
23947
|
+
duration,
|
|
23948
|
+
className,
|
|
23949
|
+
children
|
|
23950
|
+
}) {
|
|
23951
|
+
const ref = useRef(null);
|
|
23952
|
+
const [visible, setVisible] = useState(preset === "fadeIn" || preset === "slideUp");
|
|
23953
|
+
useEffect(() => {
|
|
23954
|
+
if (preset === "fadeIn" || preset === "slideUp") {
|
|
23955
|
+
const t = window.setTimeout(() => setVisible(true), delay);
|
|
23956
|
+
return () => window.clearTimeout(t);
|
|
23957
|
+
}
|
|
23958
|
+
const el = ref.current;
|
|
23959
|
+
if (!el || typeof IntersectionObserver === "undefined") {
|
|
23960
|
+
setVisible(true);
|
|
23961
|
+
return;
|
|
23962
|
+
}
|
|
23963
|
+
const io = new IntersectionObserver(
|
|
23964
|
+
(entries) => {
|
|
23965
|
+
for (const entry of entries) {
|
|
23966
|
+
if (entry.isIntersecting) {
|
|
23967
|
+
setVisible(true);
|
|
23968
|
+
io.disconnect();
|
|
23969
|
+
}
|
|
23970
|
+
}
|
|
23971
|
+
},
|
|
23972
|
+
{ threshold: 0.15 }
|
|
23973
|
+
);
|
|
23974
|
+
io.observe(el);
|
|
23975
|
+
return () => io.disconnect();
|
|
23976
|
+
}, [preset, delay]);
|
|
23977
|
+
const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
23978
|
+
const ms = duration ?? DEFAULT_DURATION[preset];
|
|
23979
|
+
const transition = reduced ? void 0 : `opacity ${ms}ms ease-out ${delay}ms, transform ${ms}ms ease-out ${delay}ms`;
|
|
23980
|
+
const hiddenTransform = preset === "slideUp" || preset === "scrollReveal" ? "translateY(24px)" : "none";
|
|
23981
|
+
const style = reduced ? void 0 : {
|
|
23982
|
+
opacity: visible ? 1 : 0,
|
|
23983
|
+
transform: visible ? "none" : hiddenTransform,
|
|
23984
|
+
transition,
|
|
23985
|
+
willChange: "opacity, transform"
|
|
23986
|
+
};
|
|
23987
|
+
if (preset === "stagger") {
|
|
23988
|
+
return /* @__PURE__ */ jsx(
|
|
23989
|
+
"div",
|
|
23990
|
+
{
|
|
23991
|
+
ref,
|
|
23992
|
+
className,
|
|
23993
|
+
"data-setto-animation": preset,
|
|
23994
|
+
"data-setto-animation-visible": visible ? "1" : "0",
|
|
23995
|
+
children: wrapChildrenForStagger(children, visible, ms, delay, reduced)
|
|
23996
|
+
}
|
|
23997
|
+
);
|
|
23998
|
+
}
|
|
23999
|
+
return /* @__PURE__ */ jsx(
|
|
24000
|
+
"div",
|
|
24001
|
+
{
|
|
24002
|
+
ref,
|
|
24003
|
+
className,
|
|
24004
|
+
style,
|
|
24005
|
+
"data-setto-animation": preset,
|
|
24006
|
+
"data-setto-animation-visible": visible ? "1" : "0",
|
|
24007
|
+
children
|
|
24008
|
+
}
|
|
24009
|
+
);
|
|
24010
|
+
}
|
|
24011
|
+
function wrapChildrenForStagger(children, visible, ms, baseDelay, reduced) {
|
|
24012
|
+
if (reduced) return children;
|
|
24013
|
+
const arr = Array.isArray(children) ? children : [children];
|
|
24014
|
+
const step = 80;
|
|
24015
|
+
return arr.map((child, i) => /* @__PURE__ */ jsx(
|
|
24016
|
+
"div",
|
|
24017
|
+
{
|
|
24018
|
+
style: {
|
|
24019
|
+
opacity: visible ? 1 : 0,
|
|
24020
|
+
transform: visible ? "none" : "translateY(16px)",
|
|
24021
|
+
transition: `opacity ${ms}ms ease-out ${baseDelay + i * step}ms, transform ${ms}ms ease-out ${baseDelay + i * step}ms`
|
|
24022
|
+
},
|
|
24023
|
+
children: child
|
|
24024
|
+
},
|
|
24025
|
+
i
|
|
24026
|
+
));
|
|
24027
|
+
}
|
|
23789
24028
|
function authCallbackType() {
|
|
23790
24029
|
const hash = window.location.hash.replace(/^#/, "");
|
|
23791
24030
|
if (!hash) return null;
|
|
@@ -23804,10 +24043,10 @@ function consumeInviteErrorFromUrl() {
|
|
|
23804
24043
|
params.delete("setto_invite_error");
|
|
23805
24044
|
const qs = params.toString();
|
|
23806
24045
|
window.history.replaceState(null, "", `${window.location.pathname}${qs ? `?${qs}` : ""}`);
|
|
23807
|
-
return code === "invalid" ? "
|
|
24046
|
+
return code === "invalid" ? "Lenken er ugyldig eller utløpt. Be om en ny nullstilling eller invitasjon." : "Kunne ikke aktivere lenken.";
|
|
23808
24047
|
}
|
|
23809
24048
|
function AuthGate({ children }) {
|
|
23810
|
-
const { supabase, session, authLoading } = useSetto();
|
|
24049
|
+
const { supabase, api, config, session, authLoading } = useSetto();
|
|
23811
24050
|
const [mode, setMode] = useState("signin");
|
|
23812
24051
|
const [email, setEmail] = useState("");
|
|
23813
24052
|
const [password, setPassword] = useState("");
|
|
@@ -23858,10 +24097,7 @@ function AuthGate({ children }) {
|
|
|
23858
24097
|
setError(null);
|
|
23859
24098
|
setInfo(null);
|
|
23860
24099
|
try {
|
|
23861
|
-
|
|
23862
|
-
redirectTo: adminRedirectUrl()
|
|
23863
|
-
});
|
|
23864
|
-
if (resetError) throw resetError;
|
|
24100
|
+
await api.requestPasswordReset(config.siteId, email);
|
|
23865
24101
|
setInfo("Vi har sendt en lenke for å nullstille passordet. Sjekk innboksen.");
|
|
23866
24102
|
} catch (err) {
|
|
23867
24103
|
setError(err instanceof Error ? err.message : "Ukjent feil");
|
|
@@ -24386,20 +24622,504 @@ function depDotStyle(status) {
|
|
|
24386
24622
|
flexShrink: 0
|
|
24387
24623
|
};
|
|
24388
24624
|
}
|
|
24625
|
+
const BLOCK_REGISTRY = {
|
|
24626
|
+
section: { allowedChildren: "any", themed: true, selectable: true },
|
|
24627
|
+
block: { allowedChildren: "any", themed: true, selectable: true },
|
|
24628
|
+
container: { allowedChildren: "any", themed: false, selectable: false },
|
|
24629
|
+
animation: { allowedChildren: "any", themed: false, selectable: false },
|
|
24630
|
+
repeater: { allowedChildren: "any", themed: false, selectable: false },
|
|
24631
|
+
text: { allowedChildren: null, themed: false, selectable: false },
|
|
24632
|
+
image: { allowedChildren: null, themed: false, selectable: false },
|
|
24633
|
+
icon: { allowedChildren: null, themed: false, selectable: false },
|
|
24634
|
+
form: { allowedChildren: null, themed: false, selectable: false },
|
|
24635
|
+
video: { allowedChildren: null, themed: false, selectable: false }
|
|
24636
|
+
};
|
|
24637
|
+
const BLOCK_TYPES = Object.keys(
|
|
24638
|
+
BLOCK_REGISTRY
|
|
24639
|
+
);
|
|
24640
|
+
function nodeChildren(node) {
|
|
24641
|
+
switch (node.type) {
|
|
24642
|
+
case "section":
|
|
24643
|
+
case "block":
|
|
24644
|
+
case "container":
|
|
24645
|
+
case "animation":
|
|
24646
|
+
return node.children;
|
|
24647
|
+
case "repeater":
|
|
24648
|
+
return node.itemTemplate;
|
|
24649
|
+
default:
|
|
24650
|
+
return [];
|
|
24651
|
+
}
|
|
24652
|
+
}
|
|
24653
|
+
const FIELD_TYPES = ["text", "email", "tel", "textarea"];
|
|
24654
|
+
const ANIMATION_PRESETS = ["fadeIn", "slideUp", "stagger", "scrollReveal"];
|
|
24655
|
+
function validateBlockTree(input) {
|
|
24656
|
+
const issues = [];
|
|
24657
|
+
if (!isRecord(input)) {
|
|
24658
|
+
return fail("$", "Block tree must be an object");
|
|
24659
|
+
}
|
|
24660
|
+
if (input.version !== "1") {
|
|
24661
|
+
issues.push({ path: "$.version", message: 'Expected version "1"' });
|
|
24662
|
+
}
|
|
24663
|
+
if (!Array.isArray(input.root)) {
|
|
24664
|
+
return fail("$.root", "root must be an array of block nodes");
|
|
24665
|
+
}
|
|
24666
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
24667
|
+
for (let i = 0; i < input.root.length; i++) {
|
|
24668
|
+
validateNode(input.root[i], `root[${i}]`, seenIds, issues);
|
|
24669
|
+
}
|
|
24670
|
+
return issues.length ? { ok: false, issues } : { ok: true };
|
|
24671
|
+
}
|
|
24672
|
+
function validateNode(raw, path, seenIds, issues) {
|
|
24673
|
+
if (!isRecord(raw)) {
|
|
24674
|
+
issues.push({ path, message: "Node must be an object" });
|
|
24675
|
+
return;
|
|
24676
|
+
}
|
|
24677
|
+
const type = raw.type;
|
|
24678
|
+
if (typeof type !== "string" || !(type in BLOCK_REGISTRY)) {
|
|
24679
|
+
issues.push({ path, message: `Unknown block type "${String(type)}"` });
|
|
24680
|
+
return;
|
|
24681
|
+
}
|
|
24682
|
+
if (typeof raw.id !== "string" || raw.id.length === 0) {
|
|
24683
|
+
issues.push({ path, message: "Node is missing a non-empty string id" });
|
|
24684
|
+
} else if (seenIds.has(raw.id)) {
|
|
24685
|
+
issues.push({ path, message: `Duplicate node id "${raw.id}"` });
|
|
24686
|
+
} else {
|
|
24687
|
+
seenIds.add(raw.id);
|
|
24688
|
+
}
|
|
24689
|
+
if (raw.className !== void 0 && typeof raw.className !== "string") {
|
|
24690
|
+
issues.push({ path: `${path}.className`, message: "className must be a string" });
|
|
24691
|
+
}
|
|
24692
|
+
validateTypeSpecific(raw, type, path, issues);
|
|
24693
|
+
const meta = BLOCK_REGISTRY[type];
|
|
24694
|
+
if (meta.allowedChildren === null && Array.isArray(raw.children) && raw.children.length > 0) {
|
|
24695
|
+
issues.push({ path, message: `Type "${type}" cannot have children` });
|
|
24696
|
+
return;
|
|
24697
|
+
}
|
|
24698
|
+
const children = nodeChildren(raw);
|
|
24699
|
+
if (meta.allowedChildren === null && children.length > 0) {
|
|
24700
|
+
issues.push({ path, message: `Type "${type}" cannot have children` });
|
|
24701
|
+
return;
|
|
24702
|
+
}
|
|
24703
|
+
for (let i = 0; i < children.length; i++) {
|
|
24704
|
+
const child = children[i];
|
|
24705
|
+
const childPath = type === "repeater" ? `${path}.itemTemplate[${i}]` : `${path}.children[${i}]`;
|
|
24706
|
+
validateNode(child, childPath, seenIds, issues);
|
|
24707
|
+
if (meta.allowedChildren !== "any" && meta.allowedChildren !== null && isRecord(child) && typeof child.type === "string" && !meta.allowedChildren.includes(child.type)) {
|
|
24708
|
+
issues.push({
|
|
24709
|
+
path: childPath,
|
|
24710
|
+
message: `Type "${child.type}" not allowed inside "${type}"`
|
|
24711
|
+
});
|
|
24712
|
+
}
|
|
24713
|
+
}
|
|
24714
|
+
}
|
|
24715
|
+
function validateTypeSpecific(raw, type, path, issues) {
|
|
24716
|
+
switch (type) {
|
|
24717
|
+
case "section":
|
|
24718
|
+
requireString(raw, "sectionId", path, issues);
|
|
24719
|
+
requireArray(raw, "children", path, issues);
|
|
24720
|
+
break;
|
|
24721
|
+
case "block":
|
|
24722
|
+
requireString(raw, "blockId", path, issues);
|
|
24723
|
+
requireArray(raw, "children", path, issues);
|
|
24724
|
+
break;
|
|
24725
|
+
case "container":
|
|
24726
|
+
requireArray(raw, "children", path, issues);
|
|
24727
|
+
break;
|
|
24728
|
+
case "text":
|
|
24729
|
+
requireString(raw, "k", path, issues);
|
|
24730
|
+
break;
|
|
24731
|
+
case "image":
|
|
24732
|
+
requireString(raw, "srcKey", path, issues);
|
|
24733
|
+
break;
|
|
24734
|
+
case "icon":
|
|
24735
|
+
requireString(raw, "k", path, issues);
|
|
24736
|
+
break;
|
|
24737
|
+
case "repeater":
|
|
24738
|
+
requireString(raw, "itemsKey", path, issues);
|
|
24739
|
+
requireArray(raw, "itemTemplate", path, issues);
|
|
24740
|
+
if (!isRecord(raw.defaultItem)) {
|
|
24741
|
+
issues.push({
|
|
24742
|
+
path: `${path}.defaultItem`,
|
|
24743
|
+
message: "defaultItem must be an object mapping field names to default strings"
|
|
24744
|
+
});
|
|
24745
|
+
} else {
|
|
24746
|
+
for (const [field, value] of Object.entries(raw.defaultItem)) {
|
|
24747
|
+
if (typeof value !== "string") {
|
|
24748
|
+
issues.push({
|
|
24749
|
+
path: `${path}.defaultItem.${field}`,
|
|
24750
|
+
message: "defaultItem values must be strings"
|
|
24751
|
+
});
|
|
24752
|
+
}
|
|
24753
|
+
}
|
|
24754
|
+
}
|
|
24755
|
+
break;
|
|
24756
|
+
case "form":
|
|
24757
|
+
requireString(raw, "formId", path, issues);
|
|
24758
|
+
if (!Array.isArray(raw.fields) || raw.fields.length === 0) {
|
|
24759
|
+
issues.push({ path: `${path}.fields`, message: "form must declare at least one field" });
|
|
24760
|
+
break;
|
|
24761
|
+
}
|
|
24762
|
+
for (let i = 0; i < raw.fields.length; i++) {
|
|
24763
|
+
validateFormField(raw.fields[i], `${path}.fields[${i}]`, issues);
|
|
24764
|
+
}
|
|
24765
|
+
if (raw.endpoint === void 0 && raw.endpointKey === void 0) {
|
|
24766
|
+
issues.push({
|
|
24767
|
+
path,
|
|
24768
|
+
message: "form must declare either endpoint or endpointKey"
|
|
24769
|
+
});
|
|
24770
|
+
}
|
|
24771
|
+
break;
|
|
24772
|
+
case "video":
|
|
24773
|
+
requireString(raw, "srcKey", path, issues);
|
|
24774
|
+
break;
|
|
24775
|
+
case "animation":
|
|
24776
|
+
requireArray(raw, "children", path, issues);
|
|
24777
|
+
if (typeof raw.preset !== "string" || !ANIMATION_PRESETS.includes(raw.preset)) {
|
|
24778
|
+
issues.push({
|
|
24779
|
+
path: `${path}.preset`,
|
|
24780
|
+
message: `preset must be one of ${ANIMATION_PRESETS.join(", ")}`
|
|
24781
|
+
});
|
|
24782
|
+
}
|
|
24783
|
+
break;
|
|
24784
|
+
}
|
|
24785
|
+
}
|
|
24786
|
+
function validateFormField(raw, path, issues) {
|
|
24787
|
+
if (!isRecord(raw)) {
|
|
24788
|
+
issues.push({ path, message: "field must be an object" });
|
|
24789
|
+
return;
|
|
24790
|
+
}
|
|
24791
|
+
if (typeof raw.name !== "string" || raw.name.length === 0) {
|
|
24792
|
+
issues.push({ path: `${path}.name`, message: "field.name must be a non-empty string" });
|
|
24793
|
+
}
|
|
24794
|
+
if (typeof raw.type !== "string" || !FIELD_TYPES.includes(raw.type)) {
|
|
24795
|
+
issues.push({
|
|
24796
|
+
path: `${path}.type`,
|
|
24797
|
+
message: `field.type must be one of ${FIELD_TYPES.join(", ")}`
|
|
24798
|
+
});
|
|
24799
|
+
}
|
|
24800
|
+
requireString(raw, "labelKey", path, issues);
|
|
24801
|
+
}
|
|
24802
|
+
function requireString(raw, field, path, issues) {
|
|
24803
|
+
const value = raw[field];
|
|
24804
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
24805
|
+
issues.push({ path: `${path}.${field}`, message: `${field} must be a non-empty string` });
|
|
24806
|
+
}
|
|
24807
|
+
}
|
|
24808
|
+
function requireArray(raw, field, path, issues) {
|
|
24809
|
+
if (!Array.isArray(raw[field])) {
|
|
24810
|
+
issues.push({ path: `${path}.${field}`, message: `${field} must be an array` });
|
|
24811
|
+
}
|
|
24812
|
+
}
|
|
24813
|
+
function isRecord(value) {
|
|
24814
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24815
|
+
}
|
|
24816
|
+
function fail(path, message) {
|
|
24817
|
+
return { ok: false, issues: [{ path, message }] };
|
|
24818
|
+
}
|
|
24819
|
+
function parseBlockTree(input) {
|
|
24820
|
+
const result = validateBlockTree(input);
|
|
24821
|
+
if (!result.ok) {
|
|
24822
|
+
const first = result.issues[0];
|
|
24823
|
+
const detail = first ? `${first.path}: ${first.message}` : "unknown";
|
|
24824
|
+
throw new Error(`Invalid block tree at ${detail}`);
|
|
24825
|
+
}
|
|
24826
|
+
return input;
|
|
24827
|
+
}
|
|
24828
|
+
const IconRegistryContext = createContext(null);
|
|
24829
|
+
const RepeaterScopeContext = createContext(null);
|
|
24830
|
+
function SettoBlockTree({ tree, icons, validate = true }) {
|
|
24831
|
+
if (validate) {
|
|
24832
|
+
const result = validateBlockTree(tree);
|
|
24833
|
+
if (!result.ok) {
|
|
24834
|
+
const first = result.issues[0];
|
|
24835
|
+
const detail = first ? `${first.path}: ${first.message}` : "unknown";
|
|
24836
|
+
throw new Error(`Invalid block tree at ${detail}`);
|
|
24837
|
+
}
|
|
24838
|
+
}
|
|
24839
|
+
return /* @__PURE__ */ jsx(IconRegistryContext.Provider, { value: { icons }, children: tree.root.map((node) => /* @__PURE__ */ jsx(RenderNode, { node }, node.id)) });
|
|
24840
|
+
}
|
|
24841
|
+
function RenderNode({ node }) {
|
|
24842
|
+
switch (node.type) {
|
|
24843
|
+
case "section":
|
|
24844
|
+
return /* @__PURE__ */ jsx(SettoSection, { sectionId: node.sectionId, className: node.className, children: renderChildren(node.children) });
|
|
24845
|
+
case "block":
|
|
24846
|
+
return /* @__PURE__ */ jsx(SettoBlock, { blockId: node.blockId, className: node.className, children: renderChildren(node.children) });
|
|
24847
|
+
case "container":
|
|
24848
|
+
return renderContainer(node.tag ?? "div", node.className, renderChildren(node.children));
|
|
24849
|
+
case "animation":
|
|
24850
|
+
return /* @__PURE__ */ jsx(
|
|
24851
|
+
SettoAnimation,
|
|
24852
|
+
{
|
|
24853
|
+
preset: node.preset,
|
|
24854
|
+
delay: node.delay,
|
|
24855
|
+
duration: node.duration,
|
|
24856
|
+
className: node.className,
|
|
24857
|
+
children: renderChildren(node.children)
|
|
24858
|
+
}
|
|
24859
|
+
);
|
|
24860
|
+
case "repeater":
|
|
24861
|
+
return /* @__PURE__ */ jsx(
|
|
24862
|
+
SettoRepeater,
|
|
24863
|
+
{
|
|
24864
|
+
itemsKey: node.itemsKey,
|
|
24865
|
+
defaultItem: node.defaultItem,
|
|
24866
|
+
itemLabel: node.itemLabel,
|
|
24867
|
+
className: node.className,
|
|
24868
|
+
children: (itemKey) => /* @__PURE__ */ jsx(
|
|
24869
|
+
RepeaterScopeContext.Provider,
|
|
24870
|
+
{
|
|
24871
|
+
value: { itemsKey: node.itemsKey, itemKey },
|
|
24872
|
+
children: renderChildren(node.itemTemplate)
|
|
24873
|
+
}
|
|
24874
|
+
)
|
|
24875
|
+
}
|
|
24876
|
+
);
|
|
24877
|
+
case "text":
|
|
24878
|
+
return /* @__PURE__ */ jsx(TextLeaf, { node });
|
|
24879
|
+
case "image":
|
|
24880
|
+
return /* @__PURE__ */ jsx(ImageLeaf, { node });
|
|
24881
|
+
case "icon":
|
|
24882
|
+
return /* @__PURE__ */ jsx(IconLeaf, { node });
|
|
24883
|
+
case "form":
|
|
24884
|
+
return /* @__PURE__ */ jsx(
|
|
24885
|
+
SettoForm,
|
|
24886
|
+
{
|
|
24887
|
+
formId: node.formId,
|
|
24888
|
+
fields: node.fields,
|
|
24889
|
+
endpoint: node.endpoint,
|
|
24890
|
+
endpointKey: node.endpointKey,
|
|
24891
|
+
className: node.className
|
|
24892
|
+
}
|
|
24893
|
+
);
|
|
24894
|
+
case "video":
|
|
24895
|
+
return /* @__PURE__ */ jsx(VideoLeaf, { node });
|
|
24896
|
+
}
|
|
24897
|
+
}
|
|
24898
|
+
function renderChildren(children) {
|
|
24899
|
+
return children.map((child) => /* @__PURE__ */ jsx(RenderNode, { node: child }, child.id));
|
|
24900
|
+
}
|
|
24901
|
+
function TextLeaf({ node }) {
|
|
24902
|
+
const k = useScopedKey(node.k);
|
|
24903
|
+
const inner = /* @__PURE__ */ jsx(T, { k });
|
|
24904
|
+
if (!node.as) {
|
|
24905
|
+
return node.className ? /* @__PURE__ */ jsx("span", { className: node.className, children: inner }) : inner;
|
|
24906
|
+
}
|
|
24907
|
+
return renderTag(node.as, node.className, inner);
|
|
24908
|
+
}
|
|
24909
|
+
function ImageLeaf({ node }) {
|
|
24910
|
+
const srcKey = useScopedKey(node.srcKey);
|
|
24911
|
+
return /* @__PURE__ */ jsx(SettoImage, { srcKey, alt: node.alt, className: node.className });
|
|
24912
|
+
}
|
|
24913
|
+
function VideoLeaf({ node }) {
|
|
24914
|
+
const srcKey = useScopedKey(node.srcKey);
|
|
24915
|
+
const posterKey = useScopedKey(node.posterKey ?? "");
|
|
24916
|
+
return /* @__PURE__ */ jsx(
|
|
24917
|
+
SettoVideo,
|
|
24918
|
+
{
|
|
24919
|
+
srcKey,
|
|
24920
|
+
posterKey: node.posterKey ? posterKey : void 0,
|
|
24921
|
+
title: node.title,
|
|
24922
|
+
className: node.className
|
|
24923
|
+
}
|
|
24924
|
+
);
|
|
24925
|
+
}
|
|
24926
|
+
function IconLeaf({ node }) {
|
|
24927
|
+
const registry = useContext(IconRegistryContext);
|
|
24928
|
+
const k = useScopedKey(node.k);
|
|
24929
|
+
if (!registry) {
|
|
24930
|
+
throw new Error("SettoBlockTree must be mounted with an `icons` prop for icon nodes");
|
|
24931
|
+
}
|
|
24932
|
+
return /* @__PURE__ */ jsx(
|
|
24933
|
+
SettoIcon,
|
|
24934
|
+
{
|
|
24935
|
+
k,
|
|
24936
|
+
icons: registry.icons,
|
|
24937
|
+
size: node.size,
|
|
24938
|
+
className: node.className
|
|
24939
|
+
}
|
|
24940
|
+
);
|
|
24941
|
+
}
|
|
24942
|
+
function useScopedKey(key) {
|
|
24943
|
+
const scope = useContext(RepeaterScopeContext);
|
|
24944
|
+
if (!scope) return key;
|
|
24945
|
+
return `${scope.itemsKey}.${scope.itemKey}.${key}`;
|
|
24946
|
+
}
|
|
24947
|
+
function renderTag(tag, className, children) {
|
|
24948
|
+
switch (tag) {
|
|
24949
|
+
case "h1":
|
|
24950
|
+
return /* @__PURE__ */ jsx("h1", { className, children });
|
|
24951
|
+
case "h2":
|
|
24952
|
+
return /* @__PURE__ */ jsx("h2", { className, children });
|
|
24953
|
+
case "h3":
|
|
24954
|
+
return /* @__PURE__ */ jsx("h3", { className, children });
|
|
24955
|
+
case "h4":
|
|
24956
|
+
return /* @__PURE__ */ jsx("h4", { className, children });
|
|
24957
|
+
case "h5":
|
|
24958
|
+
return /* @__PURE__ */ jsx("h5", { className, children });
|
|
24959
|
+
case "h6":
|
|
24960
|
+
return /* @__PURE__ */ jsx("h6", { className, children });
|
|
24961
|
+
case "p":
|
|
24962
|
+
return /* @__PURE__ */ jsx("p", { className, children });
|
|
24963
|
+
case "span":
|
|
24964
|
+
return /* @__PURE__ */ jsx("span", { className, children });
|
|
24965
|
+
case "div":
|
|
24966
|
+
return /* @__PURE__ */ jsx("div", { className, children });
|
|
24967
|
+
case "label":
|
|
24968
|
+
return /* @__PURE__ */ jsx("label", { className, children });
|
|
24969
|
+
case "em":
|
|
24970
|
+
return /* @__PURE__ */ jsx("em", { className, children });
|
|
24971
|
+
case "strong":
|
|
24972
|
+
return /* @__PURE__ */ jsx("strong", { className, children });
|
|
24973
|
+
}
|
|
24974
|
+
}
|
|
24975
|
+
function renderContainer(tag, className, children) {
|
|
24976
|
+
switch (tag) {
|
|
24977
|
+
case "div":
|
|
24978
|
+
return /* @__PURE__ */ jsx("div", { className, children });
|
|
24979
|
+
case "section":
|
|
24980
|
+
return /* @__PURE__ */ jsx("section", { className, children });
|
|
24981
|
+
case "header":
|
|
24982
|
+
return /* @__PURE__ */ jsx("header", { className, children });
|
|
24983
|
+
case "footer":
|
|
24984
|
+
return /* @__PURE__ */ jsx("footer", { className, children });
|
|
24985
|
+
case "main":
|
|
24986
|
+
return /* @__PURE__ */ jsx("main", { className, children });
|
|
24987
|
+
case "aside":
|
|
24988
|
+
return /* @__PURE__ */ jsx("aside", { className, children });
|
|
24989
|
+
case "nav":
|
|
24990
|
+
return /* @__PURE__ */ jsx("nav", { className, children });
|
|
24991
|
+
case "article":
|
|
24992
|
+
return /* @__PURE__ */ jsx("article", { className, children });
|
|
24993
|
+
case "ul":
|
|
24994
|
+
return /* @__PURE__ */ jsx("ul", { className, children });
|
|
24995
|
+
case "ol":
|
|
24996
|
+
return /* @__PURE__ */ jsx("ol", { className, children });
|
|
24997
|
+
case "li":
|
|
24998
|
+
return /* @__PURE__ */ jsx("li", { className, children });
|
|
24999
|
+
case "p":
|
|
25000
|
+
return /* @__PURE__ */ jsx("p", { className, children });
|
|
25001
|
+
case "span":
|
|
25002
|
+
return /* @__PURE__ */ jsx("span", { className, children });
|
|
25003
|
+
}
|
|
25004
|
+
}
|
|
25005
|
+
function extractContentKeys(tree) {
|
|
25006
|
+
const out = {
|
|
25007
|
+
keys: /* @__PURE__ */ new Set(),
|
|
25008
|
+
themeIds: /* @__PURE__ */ new Set(),
|
|
25009
|
+
repeaters: []
|
|
25010
|
+
};
|
|
25011
|
+
for (const node of tree.root) {
|
|
25012
|
+
walk(node, out, null);
|
|
25013
|
+
}
|
|
25014
|
+
return out;
|
|
25015
|
+
}
|
|
25016
|
+
function walk(node, out, repeaterCtx) {
|
|
25017
|
+
switch (node.type) {
|
|
25018
|
+
case "section":
|
|
25019
|
+
out.themeIds.add(node.sectionId);
|
|
25020
|
+
for (const child of node.children) walk(child, out, repeaterCtx);
|
|
25021
|
+
return;
|
|
25022
|
+
case "block":
|
|
25023
|
+
out.themeIds.add(node.blockId);
|
|
25024
|
+
for (const child of node.children) walk(child, out, repeaterCtx);
|
|
25025
|
+
return;
|
|
25026
|
+
case "container":
|
|
25027
|
+
case "animation":
|
|
25028
|
+
for (const child of node.children) walk(child, out, repeaterCtx);
|
|
25029
|
+
return;
|
|
25030
|
+
case "text":
|
|
25031
|
+
out.keys.add(scopedKey(node.k, repeaterCtx));
|
|
25032
|
+
return;
|
|
25033
|
+
case "image":
|
|
25034
|
+
out.keys.add(scopedKey(node.srcKey, repeaterCtx));
|
|
25035
|
+
return;
|
|
25036
|
+
case "icon":
|
|
25037
|
+
out.keys.add(scopedKey(node.k, repeaterCtx));
|
|
25038
|
+
return;
|
|
25039
|
+
case "video":
|
|
25040
|
+
out.keys.add(scopedKey(node.srcKey, repeaterCtx));
|
|
25041
|
+
if (node.posterKey) out.keys.add(scopedKey(node.posterKey, repeaterCtx));
|
|
25042
|
+
return;
|
|
25043
|
+
case "form":
|
|
25044
|
+
out.keys.add(`form.${node.formId}.submit`);
|
|
25045
|
+
out.keys.add(`form.${node.formId}.success`);
|
|
25046
|
+
if (node.endpointKey) out.keys.add(node.endpointKey);
|
|
25047
|
+
for (const field of node.fields) {
|
|
25048
|
+
out.keys.add(field.labelKey);
|
|
25049
|
+
if (field.placeholderKey) out.keys.add(field.placeholderKey);
|
|
25050
|
+
}
|
|
25051
|
+
return;
|
|
25052
|
+
case "repeater":
|
|
25053
|
+
recordRepeater(node, out);
|
|
25054
|
+
for (const child of node.itemTemplate) {
|
|
25055
|
+
walk(child, out, { itemsKey: node.itemsKey });
|
|
25056
|
+
}
|
|
25057
|
+
return;
|
|
25058
|
+
}
|
|
25059
|
+
}
|
|
25060
|
+
function scopedKey(key, repeaterCtx) {
|
|
25061
|
+
if (!repeaterCtx) return key;
|
|
25062
|
+
return `${repeaterCtx.itemsKey}.*.${key}`;
|
|
25063
|
+
}
|
|
25064
|
+
function recordRepeater(node, out) {
|
|
25065
|
+
const fields = Object.keys(node.defaultItem);
|
|
25066
|
+
const itemKeys = [];
|
|
25067
|
+
collectRelativeKeys(node.itemTemplate, "", itemKeys);
|
|
25068
|
+
out.repeaters.push({
|
|
25069
|
+
itemsKey: node.itemsKey,
|
|
25070
|
+
fields,
|
|
25071
|
+
itemKeys
|
|
25072
|
+
});
|
|
25073
|
+
}
|
|
25074
|
+
function collectRelativeKeys(nodes, prefix, out) {
|
|
25075
|
+
for (const node of nodes) {
|
|
25076
|
+
switch (node.type) {
|
|
25077
|
+
case "text":
|
|
25078
|
+
case "icon":
|
|
25079
|
+
out.push(node.k);
|
|
25080
|
+
break;
|
|
25081
|
+
case "image":
|
|
25082
|
+
out.push(node.srcKey);
|
|
25083
|
+
break;
|
|
25084
|
+
case "video":
|
|
25085
|
+
out.push(node.srcKey);
|
|
25086
|
+
if (node.posterKey) {
|
|
25087
|
+
out.push(node.posterKey);
|
|
25088
|
+
}
|
|
25089
|
+
break;
|
|
25090
|
+
case "section":
|
|
25091
|
+
case "block":
|
|
25092
|
+
case "container":
|
|
25093
|
+
case "animation":
|
|
25094
|
+
collectRelativeKeys(node.children, prefix, out);
|
|
25095
|
+
break;
|
|
25096
|
+
}
|
|
25097
|
+
}
|
|
25098
|
+
}
|
|
24389
25099
|
export {
|
|
24390
25100
|
AuthGate,
|
|
25101
|
+
BLOCK_REGISTRY,
|
|
25102
|
+
BLOCK_TYPES,
|
|
24391
25103
|
GuestEditProvider,
|
|
24392
25104
|
SETTO_BASE,
|
|
24393
25105
|
SettoAdminApp,
|
|
25106
|
+
SettoAnimation,
|
|
24394
25107
|
SettoBlock,
|
|
25108
|
+
SettoBlockTree,
|
|
25109
|
+
SettoForm,
|
|
24395
25110
|
SettoIcon,
|
|
24396
25111
|
SettoImage,
|
|
24397
25112
|
SettoProvider,
|
|
24398
25113
|
SettoRepeater,
|
|
24399
25114
|
SettoSection,
|
|
25115
|
+
SettoVideo,
|
|
24400
25116
|
T,
|
|
25117
|
+
extractContentKeys,
|
|
25118
|
+
nodeChildren,
|
|
25119
|
+
parseBlockTree,
|
|
24401
25120
|
useGuestEdit,
|
|
24402
25121
|
useSectionTheme,
|
|
24403
|
-
useSetto
|
|
25122
|
+
useSetto,
|
|
25123
|
+
validateBlockTree
|
|
24404
25124
|
};
|
|
24405
25125
|
//# sourceMappingURL=setto-client.js.map
|