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