grist-widget-sdk 0.4.0 → 0.5.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +79 -208
  3. package/dist/app.cjs +183 -65
  4. package/dist/app.cjs.map +1 -1
  5. package/dist/app.d.cts +5 -100
  6. package/dist/app.d.ts +5 -100
  7. package/dist/app.js +4 -2
  8. package/dist/{chunk-P6VTEKWO.js → chunk-LSALXIGM.js} +82 -10
  9. package/dist/chunk-LSALXIGM.js.map +1 -0
  10. package/dist/{chunk-O7NY46QY.js → chunk-MAPAWVQC.js} +88 -20
  11. package/dist/chunk-MAPAWVQC.js.map +1 -0
  12. package/dist/{chunk-B5ZZNLVS.js → chunk-OXA2VITC.js} +5 -1
  13. package/dist/chunk-OXA2VITC.js.map +1 -0
  14. package/dist/{use-grist-BK0eke9K.d.cts → define-widget-B1FIvI-h.d.cts} +179 -104
  15. package/dist/{use-grist-C5DMZ3wU.d.ts → define-widget-B9gEtJP0.d.ts} +179 -104
  16. package/dist/emulator/testing.cjs +2390 -2243
  17. package/dist/emulator/testing.cjs.map +1 -1
  18. package/dist/emulator/testing.d.cts +27 -9
  19. package/dist/emulator/testing.d.ts +27 -9
  20. package/dist/emulator/testing.js +74 -55
  21. package/dist/emulator/testing.js.map +1 -1
  22. package/dist/emulator.cjs +4 -0
  23. package/dist/emulator.cjs.map +1 -1
  24. package/dist/emulator.d.cts +3 -3
  25. package/dist/emulator.d.ts +3 -3
  26. package/dist/emulator.js +1 -1
  27. package/dist/index.cjs +133 -67
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +5 -1
  32. package/dist/mount-grist-widget-CkB-3iG_.d.ts +137 -0
  33. package/dist/mount-grist-widget-Dtelqao_.d.cts +137 -0
  34. package/package.json +1 -1
  35. package/dist/chunk-B5ZZNLVS.js.map +0 -1
  36. package/dist/chunk-O7NY46QY.js.map +0 -1
  37. package/dist/chunk-P6VTEKWO.js.map +0 -1
package/dist/app.cjs CHANGED
@@ -22,6 +22,7 @@ var app_exports = {};
22
22
  __export(app_exports, {
23
23
  GristWidgetApp: () => GristWidgetApp,
24
24
  deployChannelLabel: () => deployChannelLabel,
25
+ entryProps: () => entryProps,
25
26
  mountGristWidget: () => mountGristWidget,
26
27
  parseDeployPath: () => parseDeployPath,
27
28
  useReleasedVersions: () => useReleasedVersions,
@@ -30,7 +31,7 @@ __export(app_exports, {
30
31
  module.exports = __toCommonJS(app_exports);
31
32
 
32
33
  // src/app/mount-grist-widget.tsx
33
- var import_react7 = require("react");
34
+ var import_react9 = require("react");
34
35
  var import_client = require("react-dom/client");
35
36
 
36
37
  // src/sdk/use-grist.ts
@@ -579,16 +580,24 @@ function createGristApi(runtime) {
579
580
  throw err;
580
581
  }
581
582
  };
582
- const write = async (fn) => {
583
+ let running = 0;
584
+ let failure = null;
585
+ const write = (fn) => {
586
+ if (running++ === 0) failure = null;
583
587
  runtime.update({ actionStatus: "running", actionError: null });
584
- try {
585
- const out = await fn();
586
- runtime.update({ actionStatus: "idle" });
587
- return out;
588
- } catch (err) {
589
- runtime.update({ actionStatus: "error", actionError: message(err) });
590
- throw err;
591
- }
588
+ const done = (async () => {
589
+ try {
590
+ return await fn();
591
+ } catch (err) {
592
+ failure = message(err);
593
+ throw err;
594
+ } finally {
595
+ if (--running === 0) runtime.update(failure ? { actionStatus: "error", actionError: failure } : { actionStatus: "idle" });
596
+ }
597
+ })();
598
+ done.catch(() => {
599
+ });
600
+ return done;
592
601
  };
593
602
  const tableOps = (resolve) => ({
594
603
  getTableId: () => resolve().getTableId(),
@@ -693,38 +702,6 @@ function validateColumnMappings(spec, mappings) {
693
702
  };
694
703
  }
695
704
 
696
- // src/sdk/runtime/derive.ts
697
- var ACCESS_RANK = { none: 0, "read table": 1, full: 2 };
698
- function parseAccess(raw) {
699
- return raw === "none" || raw === "read table" || raw === "full" ? raw : null;
700
- }
701
- function accessLabel(level) {
702
- return level === "full" ? "Full document access" : level === "read table" ? "Read selected table" : "No document access";
703
- }
704
- function accessDenied(state) {
705
- return state.grantedAccess !== null && ACCESS_RANK[state.grantedAccess] < ACCESS_RANK[state.declaredAccess];
706
- }
707
- function deriveStatus(state) {
708
- switch (state.connection) {
709
- case "connecting":
710
- return "booting";
711
- case "not-embedded":
712
- case "unavailable":
713
- return "unavailable";
714
- case "error":
715
- return "error";
716
- case "ready":
717
- return accessDenied(state) ? "error" : "ready";
718
- }
719
- }
720
- var MAPPINGS_OK = Object.freeze({ ok: true, pending: false, missing: [], emptyMultiples: [] });
721
- var MAPPINGS_PENDING = Object.freeze({ ok: true, pending: true, missing: [], emptyMultiples: [] });
722
- function deriveColumnMappingStatus(columns, mappings) {
723
- if (!columns?.length) return MAPPINGS_OK;
724
- if (mappings === null) return MAPPINGS_PENDING;
725
- return validateColumnMappings(columns, mappings);
726
- }
727
-
728
705
  // src/sdk/runtime/cells.ts
729
706
  var ok = (value) => ({ ok: true, value });
730
707
  var bad = (message2) => ({ ok: false, message: message2 });
@@ -788,10 +765,13 @@ function checkOne(base, value) {
788
765
  return ok(value);
789
766
  }
790
767
  }
768
+ function basesOf(type) {
769
+ return (type ?? "Any").split(",").map((t) => t.trim().split(":")[0]).filter(Boolean);
770
+ }
791
771
  function checkGristCell(type, value) {
792
772
  const error = formulaError(value);
793
773
  if (error) return bad(`formula error (${error})`);
794
- const bases = (type ?? "Any").split(",").map((t) => t.trim().split(":")[0]).filter(Boolean);
774
+ const bases = basesOf(type);
795
775
  if (bases.length === 0 || bases.includes("Any")) return ok(value === void 0 ? null : value);
796
776
  let first = null;
797
777
  for (const base of bases) {
@@ -841,6 +821,38 @@ function mapAndCheck(record, columns, mappings, schema) {
841
821
  return { value: out, issues };
842
822
  }
843
823
 
824
+ // src/sdk/runtime/derive.ts
825
+ var ACCESS_RANK = { none: 0, "read table": 1, full: 2 };
826
+ function parseAccess(raw) {
827
+ return raw === "none" || raw === "read table" || raw === "full" ? raw : null;
828
+ }
829
+ function accessLabel(level) {
830
+ return level === "full" ? "Full document access" : level === "read table" ? "Read selected table" : "No document access";
831
+ }
832
+ function accessDenied(state) {
833
+ return state.grantedAccess !== null && ACCESS_RANK[state.grantedAccess] < ACCESS_RANK[state.declaredAccess];
834
+ }
835
+ function deriveStatus(state) {
836
+ switch (state.connection) {
837
+ case "connecting":
838
+ return "booting";
839
+ case "not-embedded":
840
+ case "unavailable":
841
+ return "unavailable";
842
+ case "error":
843
+ return "error";
844
+ case "ready":
845
+ return accessDenied(state) ? "error" : "ready";
846
+ }
847
+ }
848
+ var MAPPINGS_OK = Object.freeze({ ok: true, pending: false, missing: [], emptyMultiples: [] });
849
+ var MAPPINGS_PENDING = Object.freeze({ ok: true, pending: true, missing: [], emptyMultiples: [] });
850
+ function deriveColumnMappingStatus(columns, mappings) {
851
+ if (!columns?.length) return MAPPINGS_OK;
852
+ if (mappings === null) return MAPPINGS_PENDING;
853
+ return validateColumnMappings(columns, mappings);
854
+ }
855
+
844
856
  // src/sdk/helpers/grist-widget-interaction.ts
845
857
  function parseGristWidgetLinkingInfo(raw) {
846
858
  if (!raw || typeof raw !== "object" || !("asTarget" in raw)) return void 0;
@@ -1213,6 +1225,11 @@ function useNearestGristState() {
1213
1225
  return { connection, state, cellIssues: issues };
1214
1226
  }
1215
1227
 
1228
+ // src/sdk/define-widget.ts
1229
+ function isGristWidget(value) {
1230
+ return typeof value === "object" && value !== null && "definition" in value && "useGrist" in value;
1231
+ }
1232
+
1216
1233
  // src/sdk/components/grist-widget-provider.tsx
1217
1234
  var import_react2 = require("react");
1218
1235
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -1371,6 +1388,40 @@ function GristBoundary({ children }) {
1371
1388
  );
1372
1389
  }
1373
1390
 
1391
+ // src/sdk/use-widget-metadata.ts
1392
+ var import_react4 = require("react");
1393
+ function useWidgetMetadata(input) {
1394
+ (0, import_react4.useEffect)(() => {
1395
+ if (!input) return;
1396
+ const { title, description } = input;
1397
+ const canonicalUrl = window.location.href;
1398
+ document.title = title;
1399
+ setMetaTag("name", "title", title);
1400
+ setMetaTag("name", "description", description);
1401
+ setMetaTag("property", "og:title", title);
1402
+ setMetaTag("property", "og:description", description);
1403
+ setMetaTag("property", "og:type", "website");
1404
+ setMetaTag("property", "og:url", canonicalUrl);
1405
+ setMetaTag("name", "twitter:card", "summary");
1406
+ setMetaTag("name", "twitter:title", title);
1407
+ setMetaTag("name", "twitter:description", description);
1408
+ }, [input]);
1409
+ }
1410
+ function setMetaTag(key, value, content) {
1411
+ let meta = document.head.querySelector(
1412
+ `meta[${key}="${value}"]`
1413
+ );
1414
+ if (!meta) {
1415
+ meta = document.createElement("meta");
1416
+ meta.setAttribute(key, value);
1417
+ document.head.appendChild(meta);
1418
+ }
1419
+ meta.setAttribute("content", content);
1420
+ }
1421
+
1422
+ // src/app/chrome/local-dev-page.tsx
1423
+ var import_react5 = require("react");
1424
+
1374
1425
  // src/app/chrome/tokens.ts
1375
1426
  var token = (name, fallback) => `var(--${name}, ${fallback})`;
1376
1427
  var tokens = {
@@ -1451,7 +1502,20 @@ function widgetTitle() {
1451
1502
 
1452
1503
  // src/app/chrome/local-dev-page.tsx
1453
1504
  var import_jsx_runtime3 = require("react/jsx-runtime");
1505
+ function useHasDemo() {
1506
+ const [has, setHas] = (0, import_react5.useState)(null);
1507
+ (0, import_react5.useEffect)(() => {
1508
+ if (typeof fetch !== "function") return;
1509
+ let live = true;
1510
+ fetch("demo/demo.grist", { method: "HEAD" }).then((res) => live && setHas(res.ok)).catch(() => live && setHas(false));
1511
+ return () => {
1512
+ live = false;
1513
+ };
1514
+ }, []);
1515
+ return has;
1516
+ }
1454
1517
  function LocalDevPage() {
1518
+ const hasDemo = useHasDemo();
1455
1519
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { style: pageStyle, children: [
1456
1520
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: eyebrowStyle, children: [
1457
1521
  widgetTitle(),
@@ -1459,6 +1523,29 @@ function LocalDevPage() {
1459
1523
  ] }),
1460
1524
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { style: headingStyle, children: "Not embedded in Grist" }),
1461
1525
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: leadStyle, children: "This is the local dev server: there's no Grist here to render the widget against." }),
1526
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
1527
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Open it in Grist, in this browser" }),
1528
+ hasDemo === false ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
1529
+ "Run ",
1530
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "pnpm demo --save" }),
1531
+ " once (it needs Docker): it writes",
1532
+ " ",
1533
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "public/demo/" }),
1534
+ ", a document for this widget. Then",
1535
+ " ",
1536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: "demo/index.html", children: "demo/index.html" }),
1537
+ " opens it in real Grist, running in the browser, on this dev server's widget: edits reload in place."
1538
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
1539
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: "demo/index.html", children: "Open demo/index.html" }),
1540
+ ": real Grist running in the browser (no server, nothing saved), with this dev server's widget, so edits reload in place. Its document is",
1541
+ " ",
1542
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "public/demo/demo.grist" }),
1543
+ ": after changing the declared columns, run",
1544
+ " ",
1545
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "pnpm demo --save" }),
1546
+ " (Docker) to regenerate it."
1547
+ ] })
1548
+ ] }),
1462
1549
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
1463
1550
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Check it against real Grist" }),
1464
1551
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
@@ -1486,7 +1573,7 @@ function LocalDevPage() {
1486
1573
  }
1487
1574
 
1488
1575
  // src/app/chrome/release-page.tsx
1489
- var import_react5 = require("react");
1576
+ var import_react7 = require("react");
1490
1577
 
1491
1578
  // src/app/deploy-path.ts
1492
1579
  var VERSION_SEGMENT = /^v(\d+\.\d+\.\d+(?:-[\w.]+)?)$/;
@@ -1511,11 +1598,11 @@ function withTrailingSlash(path) {
1511
1598
  }
1512
1599
 
1513
1600
  // src/app/released-versions.ts
1514
- var import_react4 = require("react");
1601
+ var import_react6 = require("react");
1515
1602
  function useReleasedVersions(pathname = typeof window === "undefined" ? "/" : window.location.pathname) {
1516
1603
  const url = `${parseDeployPath(pathname).basePath}versions.json`;
1517
- const [result, setResult] = (0, import_react4.useState)({ versions: null, error: false });
1518
- (0, import_react4.useEffect)(() => {
1604
+ const [result, setResult] = (0, import_react6.useState)({ versions: null, error: false });
1605
+ (0, import_react6.useEffect)(() => {
1519
1606
  let cancelled = false;
1520
1607
  fetch(url, { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`))).then((data) => {
1521
1608
  if (cancelled) return;
@@ -1590,7 +1677,7 @@ function ChannelLink({
1590
1677
  }
1591
1678
  function InstallUrl() {
1592
1679
  const url = window.location.href;
1593
- const [copied, setCopied] = (0, import_react5.useState)(false);
1680
+ const [copied, setCopied] = (0, import_react7.useState)(false);
1594
1681
  function copy() {
1595
1682
  void navigator.clipboard?.writeText(url).then(() => {
1596
1683
  setCopied(true);
@@ -1809,14 +1896,14 @@ function LinkIcon() {
1809
1896
  }
1810
1897
 
1811
1898
  // src/app/widget-theme.tsx
1812
- var import_react6 = require("react");
1899
+ var import_react8 = require("react");
1813
1900
  var import_jsx_runtime6 = require("react/jsx-runtime");
1814
1901
  var STORAGE_KEY = "theme";
1815
1902
  var DARK_QUERY = "(prefers-color-scheme: dark)";
1816
1903
  var THEMES = ["light", "dark", "system"];
1817
- var ThemeContext = (0, import_react6.createContext)(null);
1904
+ var ThemeContext = (0, import_react8.createContext)(null);
1818
1905
  function useTheme() {
1819
- const value = (0, import_react6.useContext)(ThemeContext);
1906
+ const value = (0, import_react8.useContext)(ThemeContext);
1820
1907
  return value ?? { theme: "system", resolvedTheme: systemTheme(), setTheme: noop };
1821
1908
  }
1822
1909
  function EmbeddedThemeProvider({ children }) {
@@ -1824,22 +1911,22 @@ function EmbeddedThemeProvider({ children }) {
1824
1911
  const system = useSystemTheme(gristTheme === null);
1825
1912
  const resolvedTheme = gristTheme ?? system;
1826
1913
  useApplyTheme(resolvedTheme);
1827
- const value = (0, import_react6.useMemo)(
1914
+ const value = (0, import_react8.useMemo)(
1828
1915
  () => ({ theme: gristTheme ?? "system", resolvedTheme, setTheme: noop }),
1829
1916
  [gristTheme, resolvedTheme]
1830
1917
  );
1831
1918
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ThemeContext.Provider, { value, children });
1832
1919
  }
1833
1920
  function StandaloneThemeProvider({ children }) {
1834
- const [theme, setThemeState] = (0, import_react6.useState)(() => readStoredTheme() ?? "system");
1921
+ const [theme, setThemeState] = (0, import_react8.useState)(() => readStoredTheme() ?? "system");
1835
1922
  const system = useSystemTheme(theme === "system");
1836
1923
  const resolvedTheme = theme === "system" ? system : theme;
1837
1924
  useApplyTheme(resolvedTheme);
1838
- const setTheme = (0, import_react6.useCallback)((next) => {
1925
+ const setTheme = (0, import_react8.useCallback)((next) => {
1839
1926
  writeStoredTheme(next);
1840
1927
  setThemeState(next);
1841
1928
  }, []);
1842
- (0, import_react6.useEffect)(() => {
1929
+ (0, import_react8.useEffect)(() => {
1843
1930
  const onKeyDown = (event) => {
1844
1931
  if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) return;
1845
1932
  if (event.key.toLowerCase() !== "d" || isEditableTarget(event.target)) return;
@@ -1853,7 +1940,7 @@ function StandaloneThemeProvider({ children }) {
1853
1940
  window.addEventListener("keydown", onKeyDown);
1854
1941
  return () => window.removeEventListener("keydown", onKeyDown);
1855
1942
  }, []);
1856
- (0, import_react6.useEffect)(() => {
1943
+ (0, import_react8.useEffect)(() => {
1857
1944
  const onStorage = (event) => {
1858
1945
  if (event.key !== STORAGE_KEY) return;
1859
1946
  setThemeState(isTheme(event.newValue) ? event.newValue : "system");
@@ -1861,15 +1948,15 @@ function StandaloneThemeProvider({ children }) {
1861
1948
  window.addEventListener("storage", onStorage);
1862
1949
  return () => window.removeEventListener("storage", onStorage);
1863
1950
  }, []);
1864
- const value = (0, import_react6.useMemo)(
1951
+ const value = (0, import_react8.useMemo)(
1865
1952
  () => ({ theme, resolvedTheme, setTheme }),
1866
1953
  [theme, resolvedTheme, setTheme]
1867
1954
  );
1868
1955
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ThemeContext.Provider, { value, children });
1869
1956
  }
1870
1957
  function useSystemTheme(active) {
1871
- const [theme, setTheme] = (0, import_react6.useState)(systemTheme);
1872
- (0, import_react6.useEffect)(() => {
1958
+ const [theme, setTheme] = (0, import_react8.useState)(systemTheme);
1959
+ (0, import_react8.useEffect)(() => {
1873
1960
  if (!active || typeof window.matchMedia !== "function") return;
1874
1961
  const query = window.matchMedia(DARK_QUERY);
1875
1962
  const onChange = () => setTheme(query.matches ? "dark" : "light");
@@ -1880,7 +1967,7 @@ function useSystemTheme(active) {
1880
1967
  return theme;
1881
1968
  }
1882
1969
  function useApplyTheme(theme) {
1883
- (0, import_react6.useEffect)(() => {
1970
+ (0, import_react8.useEffect)(() => {
1884
1971
  const root = document.documentElement;
1885
1972
  if (root.classList.contains(theme) && !root.classList.contains(other(theme))) return;
1886
1973
  const restoreTransitions = suspendTransitions();
@@ -1933,21 +2020,41 @@ function noop() {
1933
2020
 
1934
2021
  // src/app/mount-grist-widget.tsx
1935
2022
  var import_jsx_runtime7 = require("react/jsx-runtime");
1936
- function mountGristWidget(config) {
1937
- const { root = "#root", ...props } = config;
2023
+ function entryProps(entry, dev = false) {
2024
+ const widget = isGristWidget(entry.widget) ? entry.widget : void 0;
2025
+ const options = widget?.options ?? entry.GRIST_OPTIONS;
2026
+ if (!options) {
2027
+ throw new Error(
2028
+ "mountGristWidget: src/app/App.tsx exports neither `widget` (defineGristWidget) nor `GRIST_OPTIONS`."
2029
+ );
2030
+ }
2031
+ const props = {
2032
+ App: entry.default,
2033
+ options,
2034
+ metadata: widget?.metadata ?? entry.WIDGET_METADATA,
2035
+ dev
2036
+ };
2037
+ if ("Alerts" in entry) props.Alerts = entry.Alerts;
2038
+ if (entry.LocalDevPage) props.LocalDevPage = entry.LocalDevPage;
2039
+ if (entry.ReleasePage) props.ReleasePage = entry.ReleasePage;
2040
+ return props;
2041
+ }
2042
+ function mountGristWidget(config, entryOptions = {}) {
2043
+ const { root = "#root", ...props } = "App" in config ? config : { ...entryProps(config, entryOptions.dev), root: entryOptions.root };
1938
2044
  const container = typeof root === "string" ? document.querySelector(root) : root;
1939
2045
  if (!container) {
1940
2046
  throw new Error(`mountGristWidget: no element matches "${String(root)}" to render the widget into.`);
1941
2047
  }
1942
2048
  const reactRoot = (0, import_client.createRoot)(container);
1943
2049
  reactRoot.render(
1944
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react7.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristWidgetApp, { ...props }) })
2050
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react9.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristWidgetApp, { ...props }) })
1945
2051
  );
1946
2052
  return { unmount: () => reactRoot.unmount() };
1947
2053
  }
1948
2054
  function GristWidgetApp({
1949
2055
  App,
1950
2056
  options,
2057
+ metadata,
1951
2058
  dev = false,
1952
2059
  Alerts = WidgetAlerts,
1953
2060
  LocalDevPage: LocalDevPage2 = LocalDevPage,
@@ -1955,15 +2062,25 @@ function GristWidgetApp({
1955
2062
  }) {
1956
2063
  const Wrap = Alerts ?? PassThrough;
1957
2064
  if (isEmbedded2()) {
1958
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristWidgetProvider, { options, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EmbeddedThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Wrap, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react7.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(App, {}) }) }) }) }) }) });
2065
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(GristWidgetProvider, { options, children: [
2066
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PageMetadata, { metadata }),
2067
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EmbeddedThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Wrap, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react9.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(App, {}) }) }) }) }) })
2068
+ ] });
1959
2069
  }
1960
2070
  const channel = parseDeployPath(window.location.pathname).channel;
1961
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StandaloneThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: dev ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocalDevPage2, {}) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ReleasePage2, { channel }) }) });
2071
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(StandaloneThemeProvider, { children: [
2072
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PageMetadata, { metadata }),
2073
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: dev ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocalDevPage2, {}) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ReleasePage2, { channel }) })
2074
+ ] });
1962
2075
  }
1963
2076
  var FILL = { minHeight: "100%", width: "100%" };
1964
2077
  function isEmbedded2() {
1965
2078
  return typeof window !== "undefined" && window.self !== window.top;
1966
2079
  }
2080
+ function PageMetadata({ metadata }) {
2081
+ useWidgetMetadata(metadata?.title ? { title: metadata.title, description: metadata.description ?? "" } : null);
2082
+ return null;
2083
+ }
1967
2084
  function PassThrough({ children }) {
1968
2085
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
1969
2086
  }
@@ -1971,6 +2088,7 @@ function PassThrough({ children }) {
1971
2088
  0 && (module.exports = {
1972
2089
  GristWidgetApp,
1973
2090
  deployChannelLabel,
2091
+ entryProps,
1974
2092
  mountGristWidget,
1975
2093
  parseDeployPath,
1976
2094
  useReleasedVersions,