@sevenfold/setto-client 0.10.0 → 0.11.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/dist/chrome.d.ts CHANGED
@@ -6,6 +6,42 @@ export interface SettoChromeHandshake {
6
6
  /** The host's React, so the chrome bundle ships no framework of its own. */
7
7
  react: typeof React;
8
8
  reactDom: typeof ReactDOM;
9
+ /**
10
+ * Hand back a component and the host renders it.
11
+ *
12
+ * Inverted deliberately. The first cut had the chrome create its own React
13
+ * root, which cannot work: `createRoot` lives in `react-dom/client`, not
14
+ * `react-dom`, and React 19 removed `render` altogether — so the bundle
15
+ * loaded, found neither, and mounted nothing at all.
16
+ *
17
+ * Rendering it from here is better than passing `createRoot` across would
18
+ * have been. The chrome ends up inside the host's own tree, so its hooks are
19
+ * the host's hooks with no second root to keep in step, and the error
20
+ * boundary that keeps a mistake off the customer's page lives on this side
21
+ * where it cannot be forgotten.
22
+ */
23
+ mount: (Component: React.ComponentType) => void;
24
+ /**
25
+ * The same, for the page at `/setto`.
26
+ *
27
+ * A second slot because the dashboard is not an overlay: it belongs in the
28
+ * host's own route, where the customer navigated to it. Registering nothing
29
+ * leaves the bundled dashboard in place, which is what keeps a chrome that
30
+ * fails to load from taking the admin down with it.
31
+ */
32
+ mountAdmin: (Component: React.ComponentType) => void;
33
+ /**
34
+ * The host's own `useSetto`, callable from the chrome.
35
+ *
36
+ * This is what makes the whole arrangement cheap. The chrome renders inside
37
+ * the provider's tree, so it can simply use the context — supabase, api,
38
+ * session, sites, the i18n store — rather than having each field copied
39
+ * across a handshake that could never be extended without a re-pin.
40
+ *
41
+ * It is a hook: obey the rules of hooks, and only call it from a component
42
+ * this file rendered.
43
+ */
44
+ useSetto: () => unknown;
9
45
  config: {
10
46
  siteId: string;
11
47
  apiUrl: string;
@@ -33,4 +69,9 @@ declare global {
33
69
  __settoChromeLoaded__?: boolean;
34
70
  }
35
71
  }
36
- export declare function SettoChrome(): null;
72
+ /**
73
+ * What the host should render at `/setto`, or null while the chrome has not
74
+ * offered anything. Callers fall back to their bundled dashboard.
75
+ */
76
+ export declare function useSettoAdminChrome(): React.ComponentType | null;
77
+ export declare function SettoChrome(): import("react/jsx-runtime").JSX.Element | null;
package/dist/index.d.ts CHANGED
@@ -22,6 +22,8 @@ export { SettoAnimation } from './SettoAnimation';
22
22
  export type { SettoAnimationProps } from './SettoAnimation';
23
23
  export { useSectionTheme } from './use-section-theme';
24
24
  export { SettoAdminApp } from './admin/App';
25
+ export { SettoContext } from './provider';
26
+ export type { SettoContextValue } from './provider';
25
27
  export { SETTO_BASE, adminViewUrl } from './lib/urls';
26
28
  export { AuthGate } from './edit-mode/auth-gate';
27
29
  export type { SettoConfig, BrandColor, DeploymentRow, SiteRow, ContentFile, PublishFile, PublishResult, } from './types';
@@ -5,7 +5,7 @@ import { I18nStore } from './lib/i18n-store';
5
5
  import { AssetStore } from './lib/asset-store';
6
6
  import { ThemeStore } from './lib/theme-store';
7
7
  import type { SettoConfig, SiteRow } from './types';
8
- interface SettoContextValue {
8
+ export interface SettoContextValue {
9
9
  config: SettoConfig;
10
10
  supabase: SupabaseClient;
11
11
  api: SettoApi;
@@ -26,6 +26,18 @@ interface SettoContextValue {
26
26
  themeStore: ThemeStore | null;
27
27
  assetStore: AssetStore | null;
28
28
  }
29
+ /**
30
+ * Exported for one caller: the chrome bundle.
31
+ *
32
+ * The dashboard is compiled into that bundle, so its `useSetto` reads *this*
33
+ * module's context — a different instance from the one the host provider
34
+ * fills, which would otherwise throw "must be used within SettoProvider" the
35
+ * moment it rendered. Handing it the host's value through this provider is
36
+ * what lets the same components run on either side of the boundary.
37
+ *
38
+ * Not part of the public API in any other sense. Use `useSetto`.
39
+ */
40
+ export declare const SettoContext: import("react").Context<SettoContextValue | null>;
29
41
  export interface SettoProviderProps {
30
42
  config: SettoConfig;
31
43
  children: ReactNode;
@@ -44,4 +56,3 @@ export interface SettoProviderProps {
44
56
  */
45
57
  export declare function SettoProvider({ config, children }: SettoProviderProps): import("react/jsx-runtime").JSX.Element;
46
58
  export declare function useSetto(): SettoContextValue;
47
- export {};
@@ -21364,8 +21364,45 @@ const CHROME_CONTRACT = 1;
21364
21364
  function chromeUrl(apiUrl) {
21365
21365
  return `${apiUrl.replace(/\/$/, "")}/chrome/v${CHROME_CONTRACT}.js`;
21366
21366
  }
21367
+ class ChromeBoundary extends React.Component {
21368
+ state = { dead: false };
21369
+ static getDerivedStateFromError() {
21370
+ return { dead: true };
21371
+ }
21372
+ componentDidCatch(error) {
21373
+ console.warn("[setto] chrome failed, continuing without it", error);
21374
+ }
21375
+ render() {
21376
+ return this.state.dead ? null : this.props.children;
21377
+ }
21378
+ }
21379
+ const slots = {
21380
+ admin: null,
21381
+ listeners: /* @__PURE__ */ new Set()
21382
+ };
21383
+ function publishSlot(which, Component) {
21384
+ slots[which] = Component;
21385
+ for (const listener of slots.listeners) {
21386
+ try {
21387
+ listener();
21388
+ } catch {
21389
+ }
21390
+ }
21391
+ }
21392
+ function useSettoAdminChrome() {
21393
+ const [, force] = useState(0);
21394
+ useEffect(() => {
21395
+ const listener = () => force((n) => n + 1);
21396
+ slots.listeners.add(listener);
21397
+ return () => {
21398
+ slots.listeners.delete(listener);
21399
+ };
21400
+ }, []);
21401
+ return slots.admin;
21402
+ }
21367
21403
  function SettoChrome() {
21368
21404
  const setto = useSetto();
21405
+ const [Chrome, setChrome] = useState(null);
21369
21406
  const listeners = useRef(/* @__PURE__ */ new Set());
21370
21407
  const stateRef = useRef(() => ({
21371
21408
  hasSession: false,
@@ -21412,10 +21449,15 @@ function SettoChrome() {
21412
21449
  subscribe: (listener) => {
21413
21450
  listeners.current.add(listener);
21414
21451
  return () => listeners.current.delete(listener);
21415
- }
21452
+ },
21453
+ // `setChrome(() => C)`: a component is a function, and the updater form
21454
+ // is the only way to put one into state without React calling it.
21455
+ mount: (Component) => setChrome(() => Component),
21456
+ mountAdmin: (Component) => publishSlot("admin", Component),
21457
+ useSetto
21416
21458
  };
21417
21459
  const script = document.createElement("script");
21418
- script.src = chromeUrl(setto.config.apiUrl);
21460
+ script.src = `${chromeUrl(setto.config.apiUrl)}?site=${encodeURIComponent(setto.config.siteId)}`;
21419
21461
  script.async = true;
21420
21462
  script.onerror = () => void 0;
21421
21463
  document.head.appendChild(script);
@@ -21423,7 +21465,8 @@ function SettoChrome() {
21423
21465
  } catch {
21424
21466
  }
21425
21467
  }, []);
21426
- return null;
21468
+ if (!Chrome) return null;
21469
+ return /* @__PURE__ */ jsx(ChromeBoundary, { children: /* @__PURE__ */ jsx(Chrome, {}) });
21427
21470
  }
21428
21471
  const SETTO_BASE = "/setto";
21429
21472
  function siteHomeUrl(pathname = "/") {
@@ -26542,6 +26585,8 @@ function depDotStyle(status) {
26542
26585
  };
26543
26586
  }
26544
26587
  function SettoAdminApp() {
26588
+ const Chrome = useSettoAdminChrome();
26589
+ if (Chrome) return /* @__PURE__ */ jsx(Chrome, {});
26545
26590
  return /* @__PURE__ */ jsx(AuthGate, { children: /* @__PURE__ */ jsx(SiteList, {}) });
26546
26591
  }
26547
26592
  const DEPLOYMENTS_VIEW = "deployments";
@@ -27144,6 +27189,7 @@ export {
27144
27189
  SettoBlock,
27145
27190
  SettoBlockTree,
27146
27191
  SettoChrome,
27192
+ SettoContext,
27147
27193
  SettoForm,
27148
27194
  SettoIcon,
27149
27195
  SettoImage,