@sevenfold/setto-client 0.32.0 → 0.33.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.
@@ -3,8 +3,13 @@ import type { ThemeStore } from '../lib/theme-store';
3
3
  interface EditModeShellProps {
4
4
  children: ReactNode;
5
5
  themeStore: ThemeStore | null;
6
- /** When false, children render unchanged — no portals or document layout. */
6
+ /** Editing: the document layout, the section tools and the link hint. */
7
7
  active: boolean;
8
+ /**
9
+ * The Setto menu, which an owner has whether or not they are editing: out of
10
+ * edit mode it is only the pencil. Defaults to `active`.
11
+ */
12
+ menu?: boolean;
8
13
  }
9
14
  /**
10
15
  * Edit mode: full-width page with a floating Setto FAB anchored bottom-left.
@@ -13,7 +18,7 @@ interface EditModeShellProps {
13
18
  * Always mounted around the host app so toggling edit mode does not remount
14
19
  * page content (which would replay GSAP / scroll-driven animations).
15
20
  */
16
- export declare function EditModeShell({ children, themeStore, active }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function EditModeShell({ children, themeStore, active, menu }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
17
22
  /** @deprecated Use EditModeShell */
18
23
  export declare const EditModeMount: typeof EditModeShell;
19
24
  export {};
@@ -30,6 +30,8 @@ export declare class PageStore {
30
30
  constructor(initial: PagesManifest | null);
31
31
  snapshot(): PageStoreSnapshot;
32
32
  subscribe(listener: Listener): () => void;
33
+ /** The manifest as published, to tell which sections the owner changed. */
34
+ get baselineManifest(): PagesManifest | null;
33
35
  get manifest(): PagesManifest | null;
34
36
  /** True once the site has a manifest to edit. */
35
37
  has(): boolean;
@@ -13,8 +13,20 @@ export interface SettoContextValue {
13
13
  session: Session | null;
14
14
  /** True while the initial auth state is being read from local storage. */
15
15
  authLoading: boolean;
16
- /** True when inline edit mode is active (session + site access). */
16
+ /**
17
+ * True while the owner is editing: `canEdit`, and they chose to edit. What
18
+ * makes text editable, shows hidden sections and draws the section tools.
19
+ */
17
20
  editMode: boolean;
21
+ /**
22
+ * True when this person may edit this site: signed in, a member, the site is
23
+ * paid for, and this is not `/setto`. The Setto menu is drawn whenever this
24
+ * is; outside `editMode` it shows the site as a visitor sees it.
25
+ * Absent on a client older than 0.33, where `editMode` meant this.
26
+ */
27
+ canEdit: boolean;
28
+ /** Switch editing on or off. Remembered for this site in this browser. */
29
+ setEditing: (on: boolean) => void;
18
30
  /** Site row matching `config.siteId`, or `null` if the user has no access. */
19
31
  currentSite: SiteRow | null;
20
32
  /** All sites the user can access (loaded after sign-in). */
@@ -65,13 +77,15 @@ export interface SettoProviderProps {
65
77
  * Wraps the host app. Initialises Supabase, the API client, and listens for
66
78
  * auth changes. Mounts the edit toolbar and enables inline editing when active.
67
79
  *
68
- * Edit mode is on when ALL of:
80
+ * The owner may edit (`canEdit`) when ALL of:
69
81
  * 1. There is an authenticated Supabase session.
70
82
  * 2. The user has access to `config.siteId` in the Setto sites table.
71
- * 3. The current path is not the `/setto` admin SPA.
83
+ * 3. The site is paid for (`live`).
84
+ * 4. The current path is not the `/setto` admin SPA.
72
85
  *
73
- * Once an editor signs in via `/setto`, returning visits auto-enter edit mode
74
- * for as long as the Supabase session persists.
86
+ * Once an editor signs in via `/setto` they stay signed in until they sign out
87
+ * there. Whether they are editing is their own switch (`setEditing`): the
88
+ * Setto menu's pencil turns it on, and publishing turns it off.
75
89
  */
76
90
  export declare function SettoProvider({ config, children }: SettoProviderProps): import("react/jsx-runtime").JSX.Element;
77
91
  export declare function useSetto(): SettoContextValue;
@@ -21783,6 +21783,10 @@ class PageStore {
21783
21783
  this.listeners.add(listener);
21784
21784
  return () => this.listeners.delete(listener);
21785
21785
  }
21786
+ /** The manifest as published, to tell which sections the owner changed. */
21787
+ get baselineManifest() {
21788
+ return this.baseline;
21789
+ }
21786
21790
  get manifest() {
21787
21791
  return this.data;
21788
21792
  }
@@ -22261,7 +22265,7 @@ const pillStyle = {
22261
22265
  whiteSpace: "nowrap",
22262
22266
  boxShadow: "0 8px 24px rgba(33, 23, 17, 0.35), 0 2px 6px rgba(33, 23, 17, 0.2)"
22263
22267
  };
22264
- function EditModeShell({ children, themeStore, active }) {
22268
+ function EditModeShell({ children, themeStore, active, menu = active }) {
22265
22269
  useSettoDocumentLayout(active);
22266
22270
  const Toolbar = useSettoChromeSlot("toolbar");
22267
22271
  const SectionTools = useSettoChromeSlot("section-toolbar");
@@ -22270,15 +22274,30 @@ function EditModeShell({ children, themeStore, active }) {
22270
22274
  const [hintRoot, setHintRoot] = useState(null);
22271
22275
  const [sectionToolbarRoot, setSectionToolbarRoot] = useState(null);
22272
22276
  useEffect(() => {
22273
- if (!active) {
22277
+ if (!menu) {
22274
22278
  setToolbarRoot(null);
22275
- setHintRoot(null);
22276
- setSectionToolbarRoot(null);
22277
22279
  return;
22278
22280
  }
22279
22281
  const toolbar = document.createElement("div");
22280
22282
  toolbar.id = "setto-toolbar-root";
22283
+ Object.assign(toolbar.style, {
22284
+ position: "fixed",
22285
+ right: "var(--setto-fab-inset, 16px)",
22286
+ bottom: "var(--setto-fab-inset, 16px)",
22287
+ zIndex: "2147483647"
22288
+ });
22281
22289
  document.body.appendChild(toolbar);
22290
+ setToolbarRoot(toolbar);
22291
+ return () => {
22292
+ toolbar.remove();
22293
+ };
22294
+ }, [menu]);
22295
+ useEffect(() => {
22296
+ if (!active) {
22297
+ setHintRoot(null);
22298
+ setSectionToolbarRoot(null);
22299
+ return;
22300
+ }
22282
22301
  const hint = document.createElement("div");
22283
22302
  hint.id = "setto-hint-root";
22284
22303
  hint.style.pointerEvents = "none";
@@ -22287,18 +22306,16 @@ function EditModeShell({ children, themeStore, active }) {
22287
22306
  sectionBar.id = "setto-section-toolbar-root";
22288
22307
  sectionBar.style.pointerEvents = "none";
22289
22308
  document.body.appendChild(sectionBar);
22290
- setToolbarRoot(toolbar);
22291
22309
  setHintRoot(hint);
22292
22310
  setSectionToolbarRoot(sectionBar);
22293
22311
  return () => {
22294
- toolbar.remove();
22295
22312
  hint.remove();
22296
22313
  sectionBar.remove();
22297
22314
  };
22298
22315
  }, [active]);
22299
22316
  return /* @__PURE__ */ jsxs(Fragment, { children: [
22300
22317
  children,
22301
- active && toolbarRoot ? createPortal(Toolbar ? /* @__PURE__ */ jsx(Toolbar, {}) : /* @__PURE__ */ jsx(ToolbarUnavailable, {}), toolbarRoot) : null,
22318
+ menu && toolbarRoot ? createPortal(Toolbar ? /* @__PURE__ */ jsx(Toolbar, {}) : /* @__PURE__ */ jsx(ToolbarUnavailable, {}), toolbarRoot) : null,
22302
22319
  active && hintRoot && LinkHint ? createPortal(/* @__PURE__ */ jsx(LinkHint, {}), hintRoot) : null,
22303
22320
  active && sectionToolbarRoot && themeStore && SectionTools ? createPortal(/* @__PURE__ */ jsx(SectionTools, {}), sectionToolbarRoot) : null
22304
22321
  ] });
@@ -36606,7 +36623,16 @@ function SettoProvider({ config, children }) {
36606
36623
  pageStoreRef.current = new PageStore(bundledManifest(config.pages));
36607
36624
  setStoreReady(true);
36608
36625
  }, [i18n, config.theme, config.pages]);
36609
- const editMode = !!session && access.currentSite?.status === "live" && !onAdminRoute && !authLoading && !access.loading;
36626
+ const canEdit = !!session && access.currentSite?.status === "live" && !onAdminRoute && !authLoading && !access.loading;
36627
+ const [editing, setEditingState] = useState(() => readEditing(config.siteId));
36628
+ const setEditing = useCallback(
36629
+ (on) => {
36630
+ writeEditing(config.siteId, on);
36631
+ setEditingState(on);
36632
+ },
36633
+ [config.siteId]
36634
+ );
36635
+ const editMode = canEdit && editing;
36610
36636
  const [selectedSectionId, setSelectedSectionId] = useState(null);
36611
36637
  const selectSection = useCallback((id) => setSelectedSectionId(id), []);
36612
36638
  const [guestEdit, setGuestEdit] = useState(false);
@@ -36624,6 +36650,8 @@ function SettoProvider({ config, children }) {
36624
36650
  session,
36625
36651
  authLoading,
36626
36652
  editMode,
36653
+ canEdit,
36654
+ setEditing,
36627
36655
  currentSite: access.currentSite,
36628
36656
  sites: access.sites,
36629
36657
  store: storeReady ? storeRef.current : null,
@@ -36636,13 +36664,29 @@ function SettoProvider({ config, children }) {
36636
36664
  setGuestEdit
36637
36665
  };
36638
36666
  const editShellActive = editMode && !!value.store;
36667
+ const menuShown = canEdit && !!value.store;
36639
36668
  return /* @__PURE__ */ jsx(SettoContext.Provider, { value, children: /* @__PURE__ */ jsxs(Fragment, { children: [
36640
- /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, themeStore: value.themeStore, children }),
36669
+ /* @__PURE__ */ jsx(EditModeShell, { active: editShellActive, menu: menuShown, themeStore: value.themeStore, children }),
36641
36670
  /* @__PURE__ */ jsx(SettoChrome, {}),
36642
36671
  /* @__PURE__ */ jsx(SettoChat, {}),
36643
36672
  /* @__PURE__ */ jsx(AutoLanguageSwitcher, { languages })
36644
36673
  ] }) });
36645
36674
  }
36675
+ const editingKey = (siteId) => `setto-editing:${siteId}`;
36676
+ function readEditing(siteId) {
36677
+ try {
36678
+ return window.localStorage.getItem(editingKey(siteId)) === "1";
36679
+ } catch {
36680
+ return false;
36681
+ }
36682
+ }
36683
+ function writeEditing(siteId, on) {
36684
+ try {
36685
+ if (on) window.localStorage.setItem(editingKey(siteId), "1");
36686
+ else window.localStorage.removeItem(editingKey(siteId));
36687
+ } catch {
36688
+ }
36689
+ }
36646
36690
  function useSetto() {
36647
36691
  const ctx = useContext(SettoContext);
36648
36692
  if (!ctx) {