@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.
@@ -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" ? "Invitasjonslenken er ugyldig eller utløpt. Be administratoren om en ny." : "Kunne ikke aktivere invitasjonen.";
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
- const { error: resetError } = await supabase.auth.resetPasswordForEmail(email, {
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