@sevenfold/setto-client 0.14.0 → 0.15.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.
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
- import { useState, useRef, useEffect, useMemo, useSyncExternalStore, useLayoutEffect, createContext, useContext, useCallback, forwardRef, Fragment as Fragment$1, useId, useInsertionEffect } from "react";
3
+ import { useState, useRef, useEffect, useMemo, createContext, useContext, useCallback, useLayoutEffect, useSyncExternalStore, forwardRef, Fragment as Fragment$1, useId, useInsertionEffect } from "react";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import * as ReactDOM from "react-dom";
6
6
  import { createPortal } from "react-dom";
@@ -21452,6 +21452,7 @@ function SettoChrome() {
21452
21452
  currentSite: null,
21453
21453
  sitesLoaded: false,
21454
21454
  editMode: false,
21455
+ guestEdit: false,
21455
21456
  authLoading: true
21456
21457
  }));
21457
21458
  stateRef.current = () => ({
@@ -21464,6 +21465,7 @@ function SettoChrome() {
21464
21465
  } : null,
21465
21466
  sitesLoaded: setto.sites !== null,
21466
21467
  editMode: setto.editMode,
21468
+ guestEdit: setto.guestEdit,
21467
21469
  authLoading: setto.authLoading
21468
21470
  });
21469
21471
  useEffect(() => {
@@ -21569,9 +21571,14 @@ function useSettoDocumentLayout(active) {
21569
21571
  overflow-x: clip;
21570
21572
  }
21571
21573
 
21574
+ /*
21575
+ * Anchored by its right edge, which is also what makes the menu open
21576
+ * leftwards: the pill grows and this corner does not move, so the
21577
+ * button somebody just tapped stays under their thumb.
21578
+ */
21572
21579
  html.setto-edit-mode #setto-toolbar-root {
21573
21580
  position: fixed;
21574
- left: var(--setto-fab-inset);
21581
+ right: var(--setto-fab-inset);
21575
21582
  bottom: var(--setto-fab-inset);
21576
21583
  z-index: 2147483647;
21577
21584
  }
@@ -21625,492 +21632,6 @@ function notifyLayoutChange() {
21625
21632
  window.dispatchEvent(new Event("resize"));
21626
21633
  });
21627
21634
  }
21628
- function isLinkContext(el) {
21629
- const key = el.getAttribute("data-setto-key");
21630
- if (key?.startsWith("lang.")) return false;
21631
- if (el.closest("[data-setto-no-hint]")) return false;
21632
- return !!(el.closest("a[href]") || el.closest("button"));
21633
- }
21634
- function activateLinkContext(el) {
21635
- const anchor = el.closest("a[href]");
21636
- if (anchor instanceof HTMLAnchorElement) {
21637
- anchor.click();
21638
- return;
21639
- }
21640
- const button = el.closest("button");
21641
- if (button instanceof HTMLButtonElement) {
21642
- button.click();
21643
- }
21644
- }
21645
- const QUERY = "(hover: none) and (pointer: coarse)";
21646
- function subscribe(onChange) {
21647
- const mq = window.matchMedia(QUERY);
21648
- mq.addEventListener("change", onChange);
21649
- return () => mq.removeEventListener("change", onChange);
21650
- }
21651
- function getSnapshot() {
21652
- return window.matchMedia(QUERY).matches;
21653
- }
21654
- function useCoarsePointer() {
21655
- return useSyncExternalStore(subscribe, getSnapshot, () => false);
21656
- }
21657
- const MARGIN$2 = 10;
21658
- const OFFSET$1 = 14;
21659
- function hintLabel() {
21660
- const isMac = navigator.platform.toUpperCase().includes("MAC");
21661
- return isMac ? "⌘+klikk for å åpne" : "Ctrl+klikk for å åpne";
21662
- }
21663
- function placeTooltip(anchor, width, height) {
21664
- const vw = window.innerWidth;
21665
- const vh = window.innerHeight;
21666
- let left = anchor.x + OFFSET$1;
21667
- let top = anchor.y + OFFSET$1;
21668
- if (left + width > vw - MARGIN$2) {
21669
- left = anchor.x - width - OFFSET$1;
21670
- }
21671
- if (left < MARGIN$2) {
21672
- left = MARGIN$2;
21673
- }
21674
- if (top + height > vh - MARGIN$2) {
21675
- top = anchor.y - height - OFFSET$1;
21676
- }
21677
- if (top < MARGIN$2) {
21678
- top = MARGIN$2;
21679
- }
21680
- return { left, top };
21681
- }
21682
- function EditLinkHint() {
21683
- const coarsePointer = useCoarsePointer();
21684
- const [anchor, setAnchor] = useState(null);
21685
- const [position, setPosition] = useState(null);
21686
- const tipRef = useRef(null);
21687
- const text = hintLabel();
21688
- useEffect(() => {
21689
- if (coarsePointer) return;
21690
- const onMove = (e) => {
21691
- const target = e.target;
21692
- if (!(target instanceof Element)) {
21693
- setAnchor(null);
21694
- return;
21695
- }
21696
- const editable = target.closest("[data-setto-key]");
21697
- if (!(editable instanceof HTMLElement) || !isLinkContext(editable)) {
21698
- setAnchor(null);
21699
- return;
21700
- }
21701
- setAnchor({ x: e.clientX, y: e.clientY });
21702
- };
21703
- document.addEventListener("mousemove", onMove, true);
21704
- return () => document.removeEventListener("mousemove", onMove, true);
21705
- }, [coarsePointer]);
21706
- useLayoutEffect(() => {
21707
- if (!anchor || !tipRef.current) {
21708
- setPosition(null);
21709
- return;
21710
- }
21711
- const { width, height } = tipRef.current.getBoundingClientRect();
21712
- setPosition(placeTooltip(anchor, width, height));
21713
- }, [anchor, text]);
21714
- if (coarsePointer || !anchor) return null;
21715
- const style = {
21716
- position: "fixed",
21717
- left: position?.left ?? anchor.x + OFFSET$1,
21718
- top: position?.top ?? anchor.y + OFFSET$1,
21719
- visibility: position ? "visible" : "hidden",
21720
- background: "#1a1a1a",
21721
- color: "#fff",
21722
- fontSize: 11,
21723
- padding: "4px 8px",
21724
- borderRadius: 4,
21725
- pointerEvents: "none",
21726
- whiteSpace: "nowrap",
21727
- fontFamily: "system-ui, sans-serif",
21728
- boxShadow: "0 2px 8px rgba(0,0,0,0.15)"
21729
- };
21730
- return /* @__PURE__ */ jsx("div", { ref: tipRef, style, children: text });
21731
- }
21732
- function colorsMatch(a, b) {
21733
- const na = normalizeColor(a);
21734
- const nb = normalizeColor(b);
21735
- if (!na || !nb) return false;
21736
- return na === nb;
21737
- }
21738
- function normalizeColor(color) {
21739
- const trimmed = color.trim().toLowerCase();
21740
- if (/^#[0-9a-f]{3}$/.test(trimmed)) {
21741
- const [, r, g, b] = trimmed.match(/^#(.)(.)(.)$/) ?? [];
21742
- return `#${r}${r}${g}${g}${b}${b}`;
21743
- }
21744
- if (/^#[0-9a-f]{6}$/.test(trimmed)) return trimmed;
21745
- const rgb = trimmed.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
21746
- if (rgb) {
21747
- const [, r, g, b] = rgb;
21748
- if (r === void 0 || g === void 0 || b === void 0) return null;
21749
- const hex = (n) => Number(n).toString(16).padStart(2, "0");
21750
- return `#${hex(r)}${hex(g)}${hex(b)}`;
21751
- }
21752
- return null;
21753
- }
21754
- function swatchBorder(color) {
21755
- const n = normalizeColor(color);
21756
- if (n === "#ffffff" || n === "#e6dccf") return "#ccc";
21757
- return "transparent";
21758
- }
21759
- const SectionEditContext = createContext(null);
21760
- function SectionEditProvider({ children }) {
21761
- const [selectedId, setSelectedId] = useState(null);
21762
- const selectSection = useCallback((id) => {
21763
- setSelectedId(id);
21764
- }, []);
21765
- const value = useMemo(
21766
- () => ({ selectedId, selectSection }),
21767
- [selectedId, selectSection]
21768
- );
21769
- return /* @__PURE__ */ jsx(SectionEditContext.Provider, { value, children });
21770
- }
21771
- function useSectionEdit() {
21772
- const ctx = useContext(SectionEditContext);
21773
- if (!ctx) {
21774
- throw new Error("useSectionEdit() must be used inside SectionEditProvider");
21775
- }
21776
- return ctx;
21777
- }
21778
- const SECTION_TOOLBAR_HEIGHT = 44;
21779
- const GAP = 8;
21780
- const MARGIN$1 = 8;
21781
- function computePosition(targetEl, toolbarW, toolbarH) {
21782
- const rect = targetEl.getBoundingClientRect();
21783
- let left = rect.left + (rect.width - toolbarW) / 2;
21784
- left = Math.max(MARGIN$1, Math.min(left, window.innerWidth - toolbarW - MARGIN$1));
21785
- const top = Math.max(MARGIN$1, rect.top - toolbarH - GAP);
21786
- return { top, left };
21787
- }
21788
- function BrandColorPicker({
21789
- label,
21790
- value,
21791
- brandColors,
21792
- open,
21793
- onToggle,
21794
- onSelect,
21795
- popoverPlacement
21796
- }) {
21797
- const buttonRef = useRef(null);
21798
- const popoverRef = useRef(null);
21799
- const [popoverPos, setPopoverPos] = useState(null);
21800
- const matched = brandColors.find((b) => colorsMatch(value, b.value));
21801
- const displayColor = matched?.value ?? (value || "#cccccc");
21802
- const displayLabel = matched?.label ?? label;
21803
- useLayoutEffect(() => {
21804
- if (!open || !buttonRef.current) {
21805
- setPopoverPos(null);
21806
- return;
21807
- }
21808
- const update = () => {
21809
- const btn = buttonRef.current;
21810
- const pop = popoverRef.current;
21811
- if (!btn) return;
21812
- const rect = btn.getBoundingClientRect();
21813
- const popoverH = pop?.offsetHeight ?? 120;
21814
- const popoverW = pop?.offsetWidth ?? 180;
21815
- let top = popoverPlacement === "above" ? rect.top - popoverH - 6 : rect.bottom + 6;
21816
- let left = rect.left;
21817
- if (left + popoverW > window.innerWidth - 8) {
21818
- left = window.innerWidth - popoverW - 8;
21819
- }
21820
- if (left < 8) left = 8;
21821
- if (top < 8) top = rect.bottom + 6;
21822
- if (top + popoverH > window.innerHeight - 8) {
21823
- top = rect.top - popoverH - 6;
21824
- }
21825
- setPopoverPos({ top, left });
21826
- };
21827
- update();
21828
- requestAnimationFrame(update);
21829
- window.addEventListener("scroll", update, true);
21830
- window.addEventListener("resize", update);
21831
- return () => {
21832
- window.removeEventListener("scroll", update, true);
21833
- window.removeEventListener("resize", update);
21834
- };
21835
- }, [open, popoverPlacement, brandColors.length]);
21836
- const popover = open ? /* @__PURE__ */ jsx(
21837
- "div",
21838
- {
21839
- ref: popoverRef,
21840
- role: "listbox",
21841
- "aria-label": label,
21842
- "data-setto-ui": true,
21843
- style: {
21844
- position: "fixed",
21845
- top: popoverPos?.top ?? -9999,
21846
- left: popoverPos?.left ?? 0,
21847
- visibility: popoverPos ? "visible" : "hidden",
21848
- zIndex: 2147483647,
21849
- display: "grid",
21850
- gridTemplateColumns: "repeat(4, 1fr)",
21851
- gap: 4,
21852
- padding: 8,
21853
- background: "#fff",
21854
- border: "1px solid #e0e0e0",
21855
- borderRadius: 8,
21856
- boxShadow: "0 8px 24px rgba(0,0,0,0.15)",
21857
- minWidth: 160
21858
- },
21859
- onMouseDown: (e) => e.stopPropagation(),
21860
- onClick: (e) => e.stopPropagation(),
21861
- children: brandColors.map((brand) => {
21862
- const selected = colorsMatch(value, brand.value);
21863
- return /* @__PURE__ */ jsxs(
21864
- "button",
21865
- {
21866
- type: "button",
21867
- role: "option",
21868
- "aria-selected": selected,
21869
- title: brand.label,
21870
- onClick: () => onSelect(brand.value),
21871
- style: {
21872
- display: "flex",
21873
- flexDirection: "column",
21874
- alignItems: "center",
21875
- gap: 4,
21876
- padding: 6,
21877
- background: selected ? "#f5f3ff" : "transparent",
21878
- border: selected ? "1px solid #640AFF" : "1px solid transparent",
21879
- borderRadius: 6,
21880
- cursor: "pointer"
21881
- },
21882
- children: [
21883
- /* @__PURE__ */ jsx(
21884
- "span",
21885
- {
21886
- "aria-hidden": true,
21887
- style: {
21888
- width: 24,
21889
- height: 24,
21890
- borderRadius: "50%",
21891
- background: brand.value,
21892
- border: `1px solid ${swatchBorder(brand.value)}`
21893
- }
21894
- }
21895
- ),
21896
- /* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: "#666", lineHeight: 1.2 }, children: brand.label })
21897
- ]
21898
- },
21899
- brand.value
21900
- );
21901
- })
21902
- }
21903
- ) : null;
21904
- return /* @__PURE__ */ jsxs(Fragment, { children: [
21905
- /* @__PURE__ */ jsxs(
21906
- "button",
21907
- {
21908
- ref: buttonRef,
21909
- type: "button",
21910
- "data-setto-ui": true,
21911
- onMouseDown: (e) => e.stopPropagation(),
21912
- onClick: (e) => {
21913
- e.stopPropagation();
21914
- onToggle();
21915
- },
21916
- "aria-expanded": open,
21917
- "aria-haspopup": "listbox",
21918
- "aria-label": `${label}: ${displayLabel}`,
21919
- title: `${label}: ${displayLabel}`,
21920
- style: {
21921
- display: "inline-flex",
21922
- alignItems: "center",
21923
- gap: 6,
21924
- padding: "4px 8px 4px 6px",
21925
- background: open ? "#f5f3ff" : "#f7f7f7",
21926
- border: open ? "1px solid #640AFF" : "1px solid #e0e0e0",
21927
- borderRadius: 6,
21928
- cursor: "pointer",
21929
- fontSize: 11,
21930
- color: "#444",
21931
- whiteSpace: "nowrap",
21932
- flexShrink: 0
21933
- },
21934
- children: [
21935
- /* @__PURE__ */ jsx(
21936
- "span",
21937
- {
21938
- "aria-hidden": true,
21939
- style: {
21940
- width: 18,
21941
- height: 18,
21942
- borderRadius: "50%",
21943
- background: displayColor,
21944
- border: `1px solid ${swatchBorder(displayColor)}`,
21945
- flexShrink: 0
21946
- }
21947
- }
21948
- ),
21949
- /* @__PURE__ */ jsx("span", { children: label }),
21950
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { color: "#999", fontSize: 10, lineHeight: 1 }, children: "▾" })
21951
- ]
21952
- }
21953
- ),
21954
- popover ? createPortal(popover, document.body) : null
21955
- ] });
21956
- }
21957
- function SectionToolbar() {
21958
- const { themeStore, config } = useSetto();
21959
- const { selectedId, selectSection } = useSectionEdit();
21960
- const [pos, setPos] = useState(null);
21961
- const [openField, setOpenField] = useState(null);
21962
- const barRef = useRef(null);
21963
- const schema = selectedId ? config.sectionSchemas?.[selectedId] : null;
21964
- const brandColors = config.brandColors ?? [];
21965
- useLayoutEffect(() => {
21966
- if (!selectedId) {
21967
- setPos(null);
21968
- return;
21969
- }
21970
- const el = document.querySelector(
21971
- `[data-setto-section="${selectedId}"]`
21972
- );
21973
- if (!el) {
21974
- setPos(null);
21975
- return;
21976
- }
21977
- const update = () => {
21978
- const bar = barRef.current;
21979
- if (!bar) return;
21980
- const w = bar.offsetWidth;
21981
- const h = bar.offsetHeight || SECTION_TOOLBAR_HEIGHT;
21982
- setPos(computePosition(el, w, h));
21983
- };
21984
- update();
21985
- requestAnimationFrame(update);
21986
- window.addEventListener("scroll", update, true);
21987
- window.addEventListener("resize", update);
21988
- return () => {
21989
- window.removeEventListener("scroll", update, true);
21990
- window.removeEventListener("resize", update);
21991
- };
21992
- }, [selectedId, openField, schema?.fields.length]);
21993
- useEffect(() => {
21994
- setOpenField(null);
21995
- }, [selectedId]);
21996
- useEffect(() => {
21997
- if (!openField) return;
21998
- const close = (e) => {
21999
- const target = e.target;
22000
- if (barRef.current?.contains(target)) return;
22001
- if (target instanceof Element && target.closest("[data-setto-ui]")) return;
22002
- setOpenField(null);
22003
- };
22004
- document.addEventListener("mousedown", close);
22005
- return () => document.removeEventListener("mousedown", close);
22006
- }, [openField]);
22007
- useEffect(() => {
22008
- if (!selectedId) return;
22009
- const onKey = (e) => {
22010
- if (e.key !== "Escape") return;
22011
- if (openField) {
22012
- setOpenField(null);
22013
- return;
22014
- }
22015
- selectSection(null);
22016
- };
22017
- window.addEventListener("keydown", onKey);
22018
- return () => window.removeEventListener("keydown", onKey);
22019
- }, [selectedId, selectSection, openField]);
22020
- if (!selectedId || !schema || !themeStore || brandColors.length === 0) {
22021
- return null;
22022
- }
22023
- const colors = themeStore.getSection(selectedId);
22024
- return /* @__PURE__ */ jsxs(
22025
- "div",
22026
- {
22027
- ref: barRef,
22028
- role: "toolbar",
22029
- "data-setto-ui": true,
22030
- "data-setto-theme-id": selectedId,
22031
- "aria-label": `Farger — ${schema.label}`,
22032
- style: {
22033
- position: "fixed",
22034
- top: pos?.top ?? -9999,
22035
- left: pos?.left ?? 0,
22036
- width: "max-content",
22037
- maxWidth: `calc(100vw - ${MARGIN$1 * 2}px)`,
22038
- minHeight: SECTION_TOOLBAR_HEIGHT,
22039
- zIndex: 2147483646,
22040
- display: "flex",
22041
- alignItems: "center",
22042
- gap: 8,
22043
- padding: "0 12px",
22044
- background: "#ffffff",
22045
- border: "1px solid #e0e0e0",
22046
- borderRadius: 8,
22047
- boxShadow: "0 4px 20px rgba(0,0,0,0.12)",
22048
- fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
22049
- fontSize: 12,
22050
- pointerEvents: "auto",
22051
- userSelect: "none",
22052
- visibility: pos ? "visible" : "hidden"
22053
- },
22054
- onMouseDown: (e) => e.stopPropagation(),
22055
- onClick: (e) => e.stopPropagation(),
22056
- children: [
22057
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, whiteSpace: "nowrap", flexShrink: 0 }, children: schema.label }),
22058
- /* @__PURE__ */ jsx("span", { style: { color: "#ccc", flexShrink: 0 }, children: "·" }),
22059
- /* @__PURE__ */ jsx(
22060
- "div",
22061
- {
22062
- style: {
22063
- display: "flex",
22064
- alignItems: "center",
22065
- gap: 6,
22066
- flexWrap: "nowrap"
22067
- },
22068
- children: schema.fields.map((field) => /* @__PURE__ */ jsx(
22069
- BrandColorPicker,
22070
- {
22071
- label: field.label,
22072
- value: colors[field.key] ?? "",
22073
- brandColors,
22074
- open: openField === field.key,
22075
- popoverPlacement: "below",
22076
- onToggle: () => setOpenField((prev) => prev === field.key ? null : field.key),
22077
- onSelect: (v) => {
22078
- themeStore.set(selectedId, field.key, v);
22079
- setOpenField(null);
22080
- }
22081
- },
22082
- field.key
22083
- ))
22084
- }
22085
- ),
22086
- /* @__PURE__ */ jsx(
22087
- "button",
22088
- {
22089
- type: "button",
22090
- "data-setto-ui": true,
22091
- onMouseDown: (e) => e.stopPropagation(),
22092
- onClick: (e) => {
22093
- e.stopPropagation();
22094
- selectSection(null);
22095
- },
22096
- "aria-label": "Lukk",
22097
- style: {
22098
- flexShrink: 0,
22099
- background: "transparent",
22100
- border: "none",
22101
- color: "#999",
22102
- cursor: "pointer",
22103
- fontSize: 16,
22104
- lineHeight: 1,
22105
- padding: "2px 6px"
22106
- },
22107
- children: "×"
22108
- }
22109
- )
22110
- ]
22111
- }
22112
- );
22113
- }
22114
21635
  function SettoMark({
22115
21636
  size = 24,
22116
21637
  letterColor = "#F2EBDF",
@@ -22175,6 +21696,8 @@ const pillStyle = {
22175
21696
  function EditModeShell({ children, themeStore, active }) {
22176
21697
  useSettoDocumentLayout(active);
22177
21698
  const Toolbar = useSettoChromeSlot("toolbar");
21699
+ const SectionTools = useSettoChromeSlot("section-toolbar");
21700
+ const LinkHint = useSettoChromeSlot("link-hint");
22178
21701
  const [toolbarRoot, setToolbarRoot] = useState(null);
22179
21702
  const [hintRoot, setHintRoot] = useState(null);
22180
21703
  const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
@@ -22208,8 +21731,8 @@ function EditModeShell({ children, themeStore, active }) {
22208
21731
  return /* @__PURE__ */ jsxs(Fragment, { children: [
22209
21732
  children,
22210
21733
  active && toolbarRoot ? createPortal(Toolbar ? /* @__PURE__ */ jsx(Toolbar, {}) : /* @__PURE__ */ jsx(ToolbarUnavailable, {}), toolbarRoot) : null,
22211
- active && hintRoot ? createPortal(/* @__PURE__ */ jsx(EditLinkHint, {}), hintRoot) : null,
22212
- active && sectionToolbarRoot && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), sectionToolbarRoot) : null
21734
+ active && hintRoot && LinkHint ? createPortal(/* @__PURE__ */ jsx(LinkHint, {}), hintRoot) : null,
21735
+ active && sectionToolbarRoot && themeStore && SectionTools ? createPortal(/* @__PURE__ */ jsx(SectionTools, {}), sectionToolbarRoot) : null
22213
21736
  ] });
22214
21737
  }
22215
21738
  const SettoContext = createContext(null);
@@ -22256,6 +21779,16 @@ function SettoProvider({ config, children }) {
22256
21779
  setStoreReady(true);
22257
21780
  }, [i18n, config.theme]);
22258
21781
  const editMode = !!session && !!access.currentSite && !onAdminRoute && !authLoading && !access.loading;
21782
+ const [selectedSectionId, setSelectedSectionId] = useState(null);
21783
+ const selectSection = useCallback((id) => setSelectedSectionId(id), []);
21784
+ const [guestEdit, setGuestEdit] = useState(false);
21785
+ useEffect(() => {
21786
+ if (editMode && guestEdit) setGuestEdit(false);
21787
+ }, [editMode, guestEdit]);
21788
+ useEffect(() => {
21789
+ document.documentElement.classList.toggle("setto-guest-editing", guestEdit);
21790
+ return () => document.documentElement.classList.remove("setto-guest-editing");
21791
+ }, [guestEdit]);
22259
21792
  const value = {
22260
21793
  config,
22261
21794
  supabase,
@@ -22267,10 +21800,14 @@ function SettoProvider({ config, children }) {
22267
21800
  sites: access.sites,
22268
21801
  store: storeReady ? storeRef.current : null,
22269
21802
  themeStore: storeReady ? themeStoreRef.current : null,
22270
- assetStore: storeReady ? assetStoreRef.current : null
21803
+ assetStore: storeReady ? assetStoreRef.current : null,
21804
+ selectedSectionId,
21805
+ selectSection,
21806
+ guestEdit,
21807
+ setGuestEdit
22271
21808
  };
22272
21809
  const editShellActive = editMode && !!value.store;
22273
- return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(SectionEditProvider, { children: [
21810
+ return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(Fragment, { children: [
22274
21811
  /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
22275
21812
  /* @__PURE__ */ jsx(SettoChrome, {})
22276
21813
  ] }) });
@@ -22282,36 +21819,24 @@ function useSetto() {
22282
21819
  }
22283
21820
  return ctx;
22284
21821
  }
22285
- const GuestEditContext = createContext(null);
22286
21822
  function GuestEditProvider({ children }) {
22287
- const { editMode, themeStore } = useSetto();
22288
- const { selectSection } = useSectionEdit();
22289
- const [active, setActive] = useState(false);
22290
- useEffect(() => {
22291
- if (editMode && active) setActive(false);
22292
- }, [editMode, active]);
22293
- useEffect(() => {
22294
- document.documentElement.classList.toggle("setto-guest-editing", active);
22295
- return () => document.documentElement.classList.remove("setto-guest-editing");
22296
- }, [active]);
22297
- const start = useCallback(() => setActive(true), []);
22298
- const stop = useCallback(() => {
22299
- setActive(false);
22300
- selectSection(null);
22301
- }, [selectSection]);
22302
- return /* @__PURE__ */ jsxs(GuestEditContext.Provider, { value: { active, start, stop }, children: [
21823
+ const { guestEdit, themeStore } = useSetto();
21824
+ const SectionTools = useSettoChromeSlot("section-toolbar");
21825
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22303
21826
  children,
22304
- active && themeStore ? createPortal(/* @__PURE__ */ jsx(SectionToolbar, {}), document.body) : null
21827
+ guestEdit && themeStore && SectionTools ? createPortal(/* @__PURE__ */ jsx(SectionTools, {}), document.body) : null
22305
21828
  ] });
22306
21829
  }
22307
21830
  function useGuestEdit() {
22308
- const ctx = useContext(GuestEditContext);
22309
- if (!ctx) {
22310
- return { active: false, start: () => {
22311
- }, stop: () => {
22312
- } };
22313
- }
22314
- return ctx;
21831
+ const { guestEdit, setGuestEdit, selectSection } = useSetto();
21832
+ return {
21833
+ active: guestEdit,
21834
+ start: () => setGuestEdit(true),
21835
+ stop: () => {
21836
+ setGuestEdit(false);
21837
+ selectSection(null);
21838
+ }
21839
+ };
22315
21840
  }
22316
21841
  const PREVIEW_HOST = /(^|\.)site\.setto\.no$/i;
22317
21842
  function isPreviewHost(hostname) {
@@ -22509,6 +22034,23 @@ const errorStyle = {
22509
22034
  textOverflow: "ellipsis",
22510
22035
  whiteSpace: "nowrap"
22511
22036
  };
22037
+ function isLinkContext(el) {
22038
+ const key = el.getAttribute("data-setto-key");
22039
+ if (key?.startsWith("lang.")) return false;
22040
+ if (el.closest("[data-setto-no-hint]")) return false;
22041
+ return !!(el.closest("a[href]") || el.closest("button"));
22042
+ }
22043
+ function activateLinkContext(el) {
22044
+ const anchor = el.closest("a[href]");
22045
+ if (anchor instanceof HTMLAnchorElement) {
22046
+ anchor.click();
22047
+ return;
22048
+ }
22049
+ const button = el.closest("button");
22050
+ if (button instanceof HTMLButtonElement) {
22051
+ button.click();
22052
+ }
22053
+ }
22512
22054
  const MARGIN = 10;
22513
22055
  const OFFSET = 8;
22514
22056
  const MIN_TOUCH = 44;
@@ -22623,6 +22165,18 @@ function rangeFromPoint(doc, x, y) {
22623
22165
  range.collapse(true);
22624
22166
  return range;
22625
22167
  }
22168
+ const QUERY = "(hover: none) and (pointer: coarse)";
22169
+ function subscribe(onChange) {
22170
+ const mq = window.matchMedia(QUERY);
22171
+ mq.addEventListener("change", onChange);
22172
+ return () => mq.removeEventListener("change", onChange);
22173
+ }
22174
+ function getSnapshot() {
22175
+ return window.matchMedia(QUERY).matches;
22176
+ }
22177
+ function useCoarsePointer() {
22178
+ return useSyncExternalStore(subscribe, getSnapshot, () => false);
22179
+ }
22626
22180
  function T({ k }) {
22627
22181
  const { t, i18n } = useTranslation();
22628
22182
  const { editMode, store } = useSetto();
@@ -22745,6 +22299,10 @@ function toBcp47(code) {
22745
22299
  if (base === "en") return "en";
22746
22300
  return code;
22747
22301
  }
22302
+ function useSectionEdit() {
22303
+ const { selectedSectionId, selectSection } = useSetto();
22304
+ return { selectedId: selectedSectionId, selectSection };
22305
+ }
22748
22306
  function isTextEditClick(target) {
22749
22307
  if (target.closest("[data-setto-key]")) return true;
22750
22308
  if (target.closest("[data-setto-ui]")) return true;
@@ -25750,6 +25308,7 @@ export {
25750
25308
  isDeploymentInProgress,
25751
25309
  isDeploymentStale,
25752
25310
  isFormFieldType,
25311
+ isLinkContext,
25753
25312
  isPreviewHost,
25754
25313
  nodeChildren,
25755
25314
  parseBlockTree,
@@ -25757,6 +25316,7 @@ export {
25757
25316
  readLocaleFields,
25758
25317
  settoSubmissionUrl,
25759
25318
  siteHomeUrl,
25319
+ useCoarsePointer,
25760
25320
  useDeploymentStatus,
25761
25321
  useGuestEdit,
25762
25322
  useSectionTheme,