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.d.cts CHANGED
@@ -1,103 +1,8 @@
1
- import * as react from 'react';
2
- import { ComponentType, ReactNode } from 'react';
3
- import { UseGristOptions } from './index.cjs';
1
+ export { D as DeployChannel, a as DeployPath, G as GristWidgetApp, b as GristWidgetAppProps, c as GristWidgetEntry, M as MountGristWidgetConfig, d as MountedGristWidget, e as deployChannelLabel, f as entryProps, m as mountGristWidget, p as parseDeployPath } from './mount-grist-widget-Dtelqao_.cjs';
2
+ import 'react';
3
+ import './define-widget-B1FIvI-h.cjs';
4
4
  import './types-DBStn0Dk.cjs';
5
5
 
6
- /**
7
- * Where a deployed build is being served from.
8
- *
9
- * A widget's deploy pipeline publishes the same build at its root and at
10
- * `/latest/`, a preview at `/dev/`, and each release at `/v<version>/`, all
11
- * under one base path (e.g. `/my-widget/` on GitHub Pages). Opened outside
12
- * Grist, the page tells them apart from `window.location.pathname` alone.
13
- */
14
- type DeployChannel = {
15
- kind: "latest";
16
- } | {
17
- kind: "dev";
18
- } | {
19
- kind: "version";
20
- version: string;
21
- };
22
- type DeployPath = {
23
- channel: DeployChannel;
24
- /**
25
- * The path the channels live under, always ending in `/`: the current
26
- * path without its channel segment. `"/my-widget/v1.2.0/"` gives
27
- * `"/my-widget/"`.
28
- */
29
- basePath: string;
30
- };
31
- /**
32
- * Parse a pathname into its deploy channel and base path. A path without a
33
- * channel segment (the deployed root, or a local dev server) is `latest`:
34
- * the deploy places the latest release at the root too.
35
- */
36
- declare function parseDeployPath(pathname: string): DeployPath;
37
- /** `"latest"`, `"dev"` or `"v1.2.0"`. */
38
- declare function deployChannelLabel(channel: DeployChannel): string;
39
-
40
- type GristWidgetAppProps = {
41
- /** The widget. A `React.lazy()` component works too: its code then loads only inside Grist. */
42
- App: ComponentType;
43
- /** The widget's `GRIST_OPTIONS`: access level, columns, and so on. */
44
- options: UseGristOptions;
45
- /**
46
- * Whether this is the local dev server. Pass `import.meta.env.DEV`.
47
- * Outside Grist it picks the local-dev page over the release page.
48
- */
49
- dev?: boolean;
50
- /**
51
- * Wraps the widget inside Grist, under {@link GristBoundary}. The default
52
- * lists what needs the user's attention (column mapping, access level,
53
- * failed writes) above the widget; `null` renders the widget alone.
54
- */
55
- Alerts?: ComponentType<{
56
- children: ReactNode;
57
- }> | null;
58
- /** Shown outside Grist on the local dev server. Defaults to a page pointing at `pnpm test` and the `dev` loop. */
59
- LocalDevPage?: ComponentType;
60
- /**
61
- * Shown outside Grist on a deployed URL (`/latest/`, `/dev/`, `/v<version>/`).
62
- * Defaults to a page linking every channel and version, with the URL to
63
- * paste into Grist.
64
- */
65
- ReleasePage?: ComponentType<{
66
- channel: DeployChannel;
67
- }>;
68
- };
69
- type MountGristWidgetConfig = GristWidgetAppProps & {
70
- /** The element to render into, or its CSS selector. Defaults to `#root`. */
71
- root?: Element | string;
72
- };
73
- type MountedGristWidget = {
74
- unmount: () => void;
75
- };
76
- /**
77
- * Render a widget into the page: everything `src/main.tsx` used to wire by
78
- * hand.
79
- *
80
- * ```tsx
81
- * import { mountGristWidget } from "grist-widget-sdk/app"
82
- * import "./index.css"
83
- * import App, { GRIST_OPTIONS } from "./App"
84
- *
85
- * mountGristWidget({ App, options: GRIST_OPTIONS, dev: import.meta.env.DEV })
86
- * ```
87
- *
88
- * Inside Grist, the widget renders in {@link GristWidgetProvider} and
89
- * {@link GristBoundary}, which gates on its mapped columns when it declares
90
- * any, and follows Grist's light/dark theme. Outside Grist, where there is
91
- * nothing to connect to, the page shows `LocalDevPage` on the dev server and
92
- * `ReleasePage` on a deployed URL.
93
- */
94
- declare function mountGristWidget(config: MountGristWidgetConfig): MountedGristWidget;
95
- /**
96
- * The tree {@link mountGristWidget} renders, as a component: for a test, or
97
- * for a page that manages its own React root.
98
- */
99
- declare function GristWidgetApp({ App, options, dev, Alerts, LocalDevPage, ReleasePage, }: GristWidgetAppProps): react.JSX.Element;
100
-
101
6
  type WidgetTheme = "light" | "dark" | "system";
102
7
  type ResolvedWidgetTheme = "light" | "dark";
103
8
  type UseThemeResult = {
@@ -141,5 +46,5 @@ type UseReleasedVersionsResult = {
141
46
  */
142
47
  declare function useReleasedVersions(pathname?: string): UseReleasedVersionsResult;
143
48
 
144
- export { GristWidgetApp, deployChannelLabel, mountGristWidget, parseDeployPath, useReleasedVersions, useTheme };
145
- export type { DeployChannel, DeployPath, GristWidgetAppProps, MountGristWidgetConfig, MountedGristWidget, ReleasedVersion, ResolvedWidgetTheme, UseReleasedVersionsResult, UseThemeResult, WidgetTheme };
49
+ export { useReleasedVersions, useTheme };
50
+ export type { ReleasedVersion, ResolvedWidgetTheme, UseReleasedVersionsResult, UseThemeResult, WidgetTheme };
package/dist/app.d.ts CHANGED
@@ -1,103 +1,8 @@
1
- import * as react from 'react';
2
- import { ComponentType, ReactNode } from 'react';
3
- import { UseGristOptions } from './index.js';
1
+ export { D as DeployChannel, a as DeployPath, G as GristWidgetApp, b as GristWidgetAppProps, c as GristWidgetEntry, M as MountGristWidgetConfig, d as MountedGristWidget, e as deployChannelLabel, f as entryProps, m as mountGristWidget, p as parseDeployPath } from './mount-grist-widget-CkB-3iG_.js';
2
+ import 'react';
3
+ import './define-widget-B9gEtJP0.js';
4
4
  import './types-DBStn0Dk.js';
5
5
 
6
- /**
7
- * Where a deployed build is being served from.
8
- *
9
- * A widget's deploy pipeline publishes the same build at its root and at
10
- * `/latest/`, a preview at `/dev/`, and each release at `/v<version>/`, all
11
- * under one base path (e.g. `/my-widget/` on GitHub Pages). Opened outside
12
- * Grist, the page tells them apart from `window.location.pathname` alone.
13
- */
14
- type DeployChannel = {
15
- kind: "latest";
16
- } | {
17
- kind: "dev";
18
- } | {
19
- kind: "version";
20
- version: string;
21
- };
22
- type DeployPath = {
23
- channel: DeployChannel;
24
- /**
25
- * The path the channels live under, always ending in `/`: the current
26
- * path without its channel segment. `"/my-widget/v1.2.0/"` gives
27
- * `"/my-widget/"`.
28
- */
29
- basePath: string;
30
- };
31
- /**
32
- * Parse a pathname into its deploy channel and base path. A path without a
33
- * channel segment (the deployed root, or a local dev server) is `latest`:
34
- * the deploy places the latest release at the root too.
35
- */
36
- declare function parseDeployPath(pathname: string): DeployPath;
37
- /** `"latest"`, `"dev"` or `"v1.2.0"`. */
38
- declare function deployChannelLabel(channel: DeployChannel): string;
39
-
40
- type GristWidgetAppProps = {
41
- /** The widget. A `React.lazy()` component works too: its code then loads only inside Grist. */
42
- App: ComponentType;
43
- /** The widget's `GRIST_OPTIONS`: access level, columns, and so on. */
44
- options: UseGristOptions;
45
- /**
46
- * Whether this is the local dev server. Pass `import.meta.env.DEV`.
47
- * Outside Grist it picks the local-dev page over the release page.
48
- */
49
- dev?: boolean;
50
- /**
51
- * Wraps the widget inside Grist, under {@link GristBoundary}. The default
52
- * lists what needs the user's attention (column mapping, access level,
53
- * failed writes) above the widget; `null` renders the widget alone.
54
- */
55
- Alerts?: ComponentType<{
56
- children: ReactNode;
57
- }> | null;
58
- /** Shown outside Grist on the local dev server. Defaults to a page pointing at `pnpm test` and the `dev` loop. */
59
- LocalDevPage?: ComponentType;
60
- /**
61
- * Shown outside Grist on a deployed URL (`/latest/`, `/dev/`, `/v<version>/`).
62
- * Defaults to a page linking every channel and version, with the URL to
63
- * paste into Grist.
64
- */
65
- ReleasePage?: ComponentType<{
66
- channel: DeployChannel;
67
- }>;
68
- };
69
- type MountGristWidgetConfig = GristWidgetAppProps & {
70
- /** The element to render into, or its CSS selector. Defaults to `#root`. */
71
- root?: Element | string;
72
- };
73
- type MountedGristWidget = {
74
- unmount: () => void;
75
- };
76
- /**
77
- * Render a widget into the page: everything `src/main.tsx` used to wire by
78
- * hand.
79
- *
80
- * ```tsx
81
- * import { mountGristWidget } from "grist-widget-sdk/app"
82
- * import "./index.css"
83
- * import App, { GRIST_OPTIONS } from "./App"
84
- *
85
- * mountGristWidget({ App, options: GRIST_OPTIONS, dev: import.meta.env.DEV })
86
- * ```
87
- *
88
- * Inside Grist, the widget renders in {@link GristWidgetProvider} and
89
- * {@link GristBoundary}, which gates on its mapped columns when it declares
90
- * any, and follows Grist's light/dark theme. Outside Grist, where there is
91
- * nothing to connect to, the page shows `LocalDevPage` on the dev server and
92
- * `ReleasePage` on a deployed URL.
93
- */
94
- declare function mountGristWidget(config: MountGristWidgetConfig): MountedGristWidget;
95
- /**
96
- * The tree {@link mountGristWidget} renders, as a component: for a test, or
97
- * for a page that manages its own React root.
98
- */
99
- declare function GristWidgetApp({ App, options, dev, Alerts, LocalDevPage, ReleasePage, }: GristWidgetAppProps): react.JSX.Element;
100
-
101
6
  type WidgetTheme = "light" | "dark" | "system";
102
7
  type ResolvedWidgetTheme = "light" | "dark";
103
8
  type UseThemeResult = {
@@ -141,5 +46,5 @@ type UseReleasedVersionsResult = {
141
46
  */
142
47
  declare function useReleasedVersions(pathname?: string): UseReleasedVersionsResult;
143
48
 
144
- export { GristWidgetApp, deployChannelLabel, mountGristWidget, parseDeployPath, useReleasedVersions, useTheme };
145
- export type { DeployChannel, DeployPath, GristWidgetAppProps, MountGristWidgetConfig, MountedGristWidget, ReleasedVersion, ResolvedWidgetTheme, UseReleasedVersionsResult, UseThemeResult, WidgetTheme };
49
+ export { useReleasedVersions, useTheme };
50
+ export type { ReleasedVersion, ResolvedWidgetTheme, UseReleasedVersionsResult, UseThemeResult, WidgetTheme };
package/dist/app.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  GristWidgetApp,
3
3
  deployChannelLabel,
4
+ entryProps,
4
5
  mountGristWidget,
5
6
  parseDeployPath,
6
7
  useReleasedVersions,
7
8
  useTheme
8
- } from "./chunk-P6VTEKWO.js";
9
- import "./chunk-O7NY46QY.js";
9
+ } from "./chunk-LSALXIGM.js";
10
+ import "./chunk-MAPAWVQC.js";
10
11
  import "./chunk-4ZGVKGDF.js";
11
12
  export {
12
13
  GristWidgetApp,
13
14
  deployChannelLabel,
15
+ entryProps,
14
16
  mountGristWidget,
15
17
  parseDeployPath,
16
18
  useReleasedVersions,
@@ -4,9 +4,11 @@ import {
4
4
  deriveColumnMappingStatus,
5
5
  deriveStatus,
6
6
  isAccessInsufficientError,
7
+ isGristWidget,
7
8
  parseGristWidgetLinkingInfo,
8
- useNearestGristState
9
- } from "./chunk-O7NY46QY.js";
9
+ useNearestGristState,
10
+ useWidgetMetadata
11
+ } from "./chunk-MAPAWVQC.js";
10
12
 
11
13
  // src/app/deploy-path.ts
12
14
  var VERSION_SEGMENT = /^v(\d+\.\d+\.\d+(?:-[\w.]+)?)$/;
@@ -178,6 +180,9 @@ function noop() {
178
180
  import { StrictMode, Suspense } from "react";
179
181
  import { createRoot } from "react-dom/client";
180
182
 
183
+ // src/app/chrome/local-dev-page.tsx
184
+ import { useEffect as useEffect3, useState as useState3 } from "react";
185
+
181
186
  // src/app/chrome/tokens.ts
182
187
  var token = (name, fallback) => `var(--${name}, ${fallback})`;
183
188
  var tokens = {
@@ -258,7 +263,20 @@ function widgetTitle() {
258
263
 
259
264
  // src/app/chrome/local-dev-page.tsx
260
265
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
266
+ function useHasDemo() {
267
+ const [has, setHas] = useState3(null);
268
+ useEffect3(() => {
269
+ if (typeof fetch !== "function") return;
270
+ let live = true;
271
+ fetch("demo/demo.grist", { method: "HEAD" }).then((res) => live && setHas(res.ok)).catch(() => live && setHas(false));
272
+ return () => {
273
+ live = false;
274
+ };
275
+ }, []);
276
+ return has;
277
+ }
261
278
  function LocalDevPage() {
279
+ const hasDemo = useHasDemo();
262
280
  return /* @__PURE__ */ jsxs("main", { style: pageStyle, children: [
263
281
  /* @__PURE__ */ jsxs("p", { style: eyebrowStyle, children: [
264
282
  widgetTitle(),
@@ -266,6 +284,29 @@ function LocalDevPage() {
266
284
  ] }),
267
285
  /* @__PURE__ */ jsx2("h1", { style: headingStyle, children: "Not embedded in Grist" }),
268
286
  /* @__PURE__ */ jsx2("p", { style: leadStyle, children: "This is the local dev server: there's no Grist here to render the widget against." }),
287
+ /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
288
+ /* @__PURE__ */ jsx2("h2", { style: cardTitleStyle, children: "Open it in Grist, in this browser" }),
289
+ hasDemo === false ? /* @__PURE__ */ jsxs("p", { style: cardTextStyle, children: [
290
+ "Run ",
291
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "pnpm demo --save" }),
292
+ " once (it needs Docker): it writes",
293
+ " ",
294
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "public/demo/" }),
295
+ ", a document for this widget. Then",
296
+ " ",
297
+ /* @__PURE__ */ jsx2("a", { href: "demo/index.html", children: "demo/index.html" }),
298
+ " opens it in real Grist, running in the browser, on this dev server's widget: edits reload in place."
299
+ ] }) : /* @__PURE__ */ jsxs("p", { style: cardTextStyle, children: [
300
+ /* @__PURE__ */ jsx2("a", { href: "demo/index.html", children: "Open demo/index.html" }),
301
+ ": real Grist running in the browser (no server, nothing saved), with this dev server's widget, so edits reload in place. Its document is",
302
+ " ",
303
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "public/demo/demo.grist" }),
304
+ ": after changing the declared columns, run",
305
+ " ",
306
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "pnpm demo --save" }),
307
+ " (Docker) to regenerate it."
308
+ ] })
309
+ ] }),
269
310
  /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
270
311
  /* @__PURE__ */ jsx2("h2", { style: cardTitleStyle, children: "Check it against real Grist" }),
271
312
  /* @__PURE__ */ jsxs("p", { style: cardTextStyle, children: [
@@ -293,7 +334,7 @@ function LocalDevPage() {
293
334
  }
294
335
 
295
336
  // src/app/chrome/release-page.tsx
296
- import { useState as useState3 } from "react";
337
+ import { useState as useState4 } from "react";
297
338
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
298
339
  function ReleasePage({ channel }) {
299
340
  const label = deployChannelLabel(channel);
@@ -352,7 +393,7 @@ function ChannelLink({
352
393
  }
353
394
  function InstallUrl() {
354
395
  const url = window.location.href;
355
- const [copied, setCopied] = useState3(false);
396
+ const [copied, setCopied] = useState4(false);
356
397
  function copy() {
357
398
  void navigator.clipboard?.writeText(url).then(() => {
358
399
  setCopied(true);
@@ -571,9 +612,28 @@ function LinkIcon() {
571
612
  }
572
613
 
573
614
  // src/app/mount-grist-widget.tsx
574
- import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
575
- function mountGristWidget(config) {
576
- const { root = "#root", ...props } = config;
615
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
616
+ function entryProps(entry, dev = false) {
617
+ const widget = isGristWidget(entry.widget) ? entry.widget : void 0;
618
+ const options = widget?.options ?? entry.GRIST_OPTIONS;
619
+ if (!options) {
620
+ throw new Error(
621
+ "mountGristWidget: src/app/App.tsx exports neither `widget` (defineGristWidget) nor `GRIST_OPTIONS`."
622
+ );
623
+ }
624
+ const props = {
625
+ App: entry.default,
626
+ options,
627
+ metadata: widget?.metadata ?? entry.WIDGET_METADATA,
628
+ dev
629
+ };
630
+ if ("Alerts" in entry) props.Alerts = entry.Alerts;
631
+ if (entry.LocalDevPage) props.LocalDevPage = entry.LocalDevPage;
632
+ if (entry.ReleasePage) props.ReleasePage = entry.ReleasePage;
633
+ return props;
634
+ }
635
+ function mountGristWidget(config, entryOptions = {}) {
636
+ const { root = "#root", ...props } = "App" in config ? config : { ...entryProps(config, entryOptions.dev), root: entryOptions.root };
577
637
  const container = typeof root === "string" ? document.querySelector(root) : root;
578
638
  if (!container) {
579
639
  throw new Error(`mountGristWidget: no element matches "${String(root)}" to render the widget into.`);
@@ -587,6 +647,7 @@ function mountGristWidget(config) {
587
647
  function GristWidgetApp({
588
648
  App,
589
649
  options,
650
+ metadata,
590
651
  dev = false,
591
652
  Alerts = WidgetAlerts,
592
653
  LocalDevPage: LocalDevPage2 = LocalDevPage,
@@ -594,15 +655,25 @@ function GristWidgetApp({
594
655
  }) {
595
656
  const Wrap = Alerts ?? PassThrough;
596
657
  if (isEmbedded()) {
597
- return /* @__PURE__ */ jsx5(GristWidgetProvider, { options, children: /* @__PURE__ */ jsx5(EmbeddedThemeProvider, { children: /* @__PURE__ */ jsx5(GristBoundary, { children: /* @__PURE__ */ jsx5("div", { style: FILL, children: /* @__PURE__ */ jsx5(Wrap, { children: /* @__PURE__ */ jsx5(Suspense, { fallback: null, children: /* @__PURE__ */ jsx5(App, {}) }) }) }) }) }) });
658
+ return /* @__PURE__ */ jsxs4(GristWidgetProvider, { options, children: [
659
+ /* @__PURE__ */ jsx5(PageMetadata, { metadata }),
660
+ /* @__PURE__ */ jsx5(EmbeddedThemeProvider, { children: /* @__PURE__ */ jsx5(GristBoundary, { children: /* @__PURE__ */ jsx5("div", { style: FILL, children: /* @__PURE__ */ jsx5(Wrap, { children: /* @__PURE__ */ jsx5(Suspense, { fallback: null, children: /* @__PURE__ */ jsx5(App, {}) }) }) }) }) })
661
+ ] });
598
662
  }
599
663
  const channel = parseDeployPath(window.location.pathname).channel;
600
- return /* @__PURE__ */ jsx5(StandaloneThemeProvider, { children: /* @__PURE__ */ jsx5("div", { style: FILL, children: dev ? /* @__PURE__ */ jsx5(LocalDevPage2, {}) : /* @__PURE__ */ jsx5(ReleasePage2, { channel }) }) });
664
+ return /* @__PURE__ */ jsxs4(StandaloneThemeProvider, { children: [
665
+ /* @__PURE__ */ jsx5(PageMetadata, { metadata }),
666
+ /* @__PURE__ */ jsx5("div", { style: FILL, children: dev ? /* @__PURE__ */ jsx5(LocalDevPage2, {}) : /* @__PURE__ */ jsx5(ReleasePage2, { channel }) })
667
+ ] });
601
668
  }
602
669
  var FILL = { minHeight: "100%", width: "100%" };
603
670
  function isEmbedded() {
604
671
  return typeof window !== "undefined" && window.self !== window.top;
605
672
  }
673
+ function PageMetadata({ metadata }) {
674
+ useWidgetMetadata(metadata?.title ? { title: metadata.title, description: metadata.description ?? "" } : null);
675
+ return null;
676
+ }
606
677
  function PassThrough({ children }) {
607
678
  return /* @__PURE__ */ jsx5(Fragment2, { children });
608
679
  }
@@ -612,7 +683,8 @@ export {
612
683
  deployChannelLabel,
613
684
  useReleasedVersions,
614
685
  useTheme,
686
+ entryProps,
615
687
  mountGristWidget,
616
688
  GristWidgetApp
617
689
  };
618
- //# sourceMappingURL=chunk-P6VTEKWO.js.map
690
+ //# sourceMappingURL=chunk-LSALXIGM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app/deploy-path.ts","../src/app/released-versions.ts","../src/app/widget-theme.tsx","../src/app/mount-grist-widget.tsx","../src/app/chrome/local-dev-page.tsx","../src/app/chrome/tokens.ts","../src/app/chrome/widget-title.ts","../src/app/chrome/release-page.tsx","../src/sdk/helpers/grist-sdk-alerts.ts","../src/app/chrome/widget-alerts.tsx"],"sourcesContent":["/**\n * Where a deployed build is being served from.\n *\n * A widget's deploy pipeline publishes the same build at its root and at\n * `/latest/`, a preview at `/dev/`, and each release at `/v<version>/`, all\n * under one base path (e.g. `/my-widget/` on GitHub Pages). Opened outside\n * Grist, the page tells them apart from `window.location.pathname` alone.\n */\nexport type DeployChannel =\n | { kind: \"latest\" }\n | { kind: \"dev\" }\n | { kind: \"version\"; version: string }\n\nexport type DeployPath = {\n channel: DeployChannel\n /**\n * The path the channels live under, always ending in `/`: the current\n * path without its channel segment. `\"/my-widget/v1.2.0/\"` gives\n * `\"/my-widget/\"`.\n */\n basePath: string\n}\n\nconst VERSION_SEGMENT = /^v(\\d+\\.\\d+\\.\\d+(?:-[\\w.]+)?)$/\n\n/**\n * Parse a pathname into its deploy channel and base path. A path without a\n * channel segment (the deployed root, or a local dev server) is `latest`:\n * the deploy places the latest release at the root too.\n */\nexport function parseDeployPath(pathname: string): DeployPath {\n const segments = pathname.replace(/\\/+$/, \"\").split(\"/\")\n const last = segments[segments.length - 1] ?? \"\"\n\n let channel: DeployChannel | null = null\n if (last === \"latest\") channel = { kind: \"latest\" }\n else if (last === \"dev\") channel = { kind: \"dev\" }\n else {\n const match = VERSION_SEGMENT.exec(last)\n if (match) channel = { kind: \"version\", version: match[1] }\n }\n\n if (!channel) return { channel: { kind: \"latest\" }, basePath: withTrailingSlash(pathname) }\n return { channel, basePath: withTrailingSlash(segments.slice(0, -1).join(\"/\")) }\n}\n\n/** `\"latest\"`, `\"dev\"` or `\"v1.2.0\"`. */\nexport function deployChannelLabel(channel: DeployChannel): string {\n return channel.kind === \"version\" ? `v${channel.version}` : channel.kind\n}\n\nfunction withTrailingSlash(path: string): string {\n return path.endsWith(\"/\") ? path : `${path}/`\n}\n","import { useEffect, useState } from \"react\"\n\nimport { parseDeployPath } from \"./deploy-path\"\n\n/** One entry of the `versions.json` a widget's deploy pipeline maintains. */\nexport type ReleasedVersion = {\n version: string\n publishedAt: string\n}\n\nexport type UseReleasedVersionsResult = {\n /** Newest first, as the deploy writes them; `null` until loaded. */\n versions: ReleasedVersion[] | null\n /** `true` when `versions.json` couldn't be fetched or read. */\n error: boolean\n}\n\n/**\n * The widget's released versions, read from the `versions.json` next to its\n * channels (see {@link parseDeployPath}). For a page shown outside Grist,\n * such as a release page linking to each version.\n */\nexport function useReleasedVersions(\n pathname: string = typeof window === \"undefined\" ? \"/\" : window.location.pathname\n): UseReleasedVersionsResult {\n const url = `${parseDeployPath(pathname).basePath}versions.json`\n const [result, setResult] = useState<UseReleasedVersionsResult>({ versions: null, error: false })\n\n useEffect(() => {\n let cancelled = false\n fetch(url, { cache: \"no-store\" })\n .then((res) => (res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`))))\n .then((data: unknown) => {\n if (cancelled) return\n if (!Array.isArray(data)) throw new Error(\"versions.json is not an array\")\n setResult({ versions: data as ReleasedVersion[], error: false })\n })\n .catch(() => {\n if (!cancelled) setResult({ versions: null, error: true })\n })\n return () => {\n cancelled = true\n }\n }, [url])\n\n return result\n}\n","import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\"\n\nimport { useNearestGristState } from \"../sdk/use-grist\"\n\nexport type WidgetTheme = \"light\" | \"dark\" | \"system\"\nexport type ResolvedWidgetTheme = \"light\" | \"dark\"\n\nexport type UseThemeResult = {\n /**\n * Inside Grist: Grist's theme, or `\"system\"` until Grist has sent it.\n * Outside Grist: the saved choice, `\"system\"` by default.\n */\n theme: WidgetTheme\n /** The theme applied to `<html class>`: `\"system\"` resolved to light or dark. */\n resolvedTheme: ResolvedWidgetTheme\n /**\n * Choose a theme outside Grist; it is saved in `localStorage`. Inside Grist\n * this does nothing: the widget follows Grist's theme.\n */\n setTheme: (theme: WidgetTheme) => void\n}\n\nconst STORAGE_KEY = \"theme\"\nconst DARK_QUERY = \"(prefers-color-scheme: dark)\"\nconst THEMES: readonly WidgetTheme[] = [\"light\", \"dark\", \"system\"]\n\nconst ThemeContext = createContext<UseThemeResult | null>(null)\n\n/**\n * The widget's current theme, for components that need it in JavaScript\n * (a toaster, a chart). Same shape as shadcn's `useTheme()`. Styles should\n * rely on the `dark` class that {@link mountGristWidget} keeps on `<html>`.\n * Outside a mounted widget (a unit test, for instance) it reports the\n * system theme and `setTheme` does nothing.\n */\nexport function useTheme(): UseThemeResult {\n const value = useContext(ThemeContext)\n return value ?? { theme: \"system\", resolvedTheme: systemTheme(), setTheme: noop }\n}\n\n/** Inside Grist: follows Grist's theme, and the OS setting until Grist sends one. */\nexport function EmbeddedThemeProvider({ children }: { children: ReactNode }) {\n const gristTheme = useNearestGristState().state.theme\n const system = useSystemTheme(gristTheme === null)\n const resolvedTheme = gristTheme ?? system\n\n useApplyTheme(resolvedTheme)\n\n const value = useMemo<UseThemeResult>(\n () => ({ theme: gristTheme ?? \"system\", resolvedTheme, setTheme: noop }),\n [gristTheme, resolvedTheme]\n )\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n}\n\n/**\n * Outside Grist (`pnpm dev`, the deployed release page): the saved choice,\n * the OS setting by default, and the `d` key to switch between light and\n * dark, as shadcn's theme provider does.\n */\nexport function StandaloneThemeProvider({ children }: { children: ReactNode }) {\n const [theme, setThemeState] = useState<WidgetTheme>(() => readStoredTheme() ?? \"system\")\n const system = useSystemTheme(theme === \"system\")\n const resolvedTheme = theme === \"system\" ? system : theme\n\n useApplyTheme(resolvedTheme)\n\n const setTheme = useCallback((next: WidgetTheme) => {\n writeStoredTheme(next)\n setThemeState(next)\n }, [])\n\n // `d` switches between light and dark, unless typing or using a shortcut.\n useEffect(() => {\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) return\n if (event.key.toLowerCase() !== \"d\" || isEditableTarget(event.target)) return\n setThemeState((current) => {\n const currentResolved = current === \"system\" ? systemTheme() : current\n const next: WidgetTheme = currentResolved === \"dark\" ? \"light\" : \"dark\"\n writeStoredTheme(next)\n return next\n })\n }\n window.addEventListener(\"keydown\", onKeyDown)\n return () => window.removeEventListener(\"keydown\", onKeyDown)\n }, [])\n\n // Another tab saving a choice applies here too.\n useEffect(() => {\n const onStorage = (event: StorageEvent) => {\n if (event.key !== STORAGE_KEY) return\n setThemeState(isTheme(event.newValue) ? event.newValue : \"system\")\n }\n window.addEventListener(\"storage\", onStorage)\n return () => window.removeEventListener(\"storage\", onStorage)\n }, [])\n\n const value = useMemo<UseThemeResult>(\n () => ({ theme, resolvedTheme, setTheme }),\n [theme, resolvedTheme, setTheme]\n )\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n}\n\n/** The OS setting, kept current while `active`. */\nfunction useSystemTheme(active: boolean): ResolvedWidgetTheme {\n const [theme, setTheme] = useState<ResolvedWidgetTheme>(systemTheme)\n\n useEffect(() => {\n if (!active || typeof window.matchMedia !== \"function\") return\n const query = window.matchMedia(DARK_QUERY)\n const onChange = () => setTheme(query.matches ? \"dark\" : \"light\")\n onChange()\n query.addEventListener(\"change\", onChange)\n return () => query.removeEventListener(\"change\", onChange)\n }, [active])\n\n return theme\n}\n\n/** Keep `light` or `dark` on `<html class>`, which Tailwind's dark variant reads. */\nfunction useApplyTheme(theme: ResolvedWidgetTheme) {\n useEffect(() => {\n const root = document.documentElement\n if (root.classList.contains(theme) && !root.classList.contains(other(theme))) return\n const restoreTransitions = suspendTransitions()\n root.classList.remove(other(theme))\n root.classList.add(theme)\n restoreTransitions()\n }, [theme])\n}\n\nfunction systemTheme(): ResolvedWidgetTheme {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return \"light\"\n return window.matchMedia(DARK_QUERY).matches ? \"dark\" : \"light\"\n}\n\nfunction other(theme: ResolvedWidgetTheme): ResolvedWidgetTheme {\n return theme === \"dark\" ? \"light\" : \"dark\"\n}\n\n/** Switch themes without every color transition animating at once. */\nfunction suspendTransitions(): () => void {\n const style = document.createElement(\"style\")\n style.appendChild(\n document.createTextNode(\"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}\")\n )\n document.head.appendChild(style)\n return () => {\n window.getComputedStyle(document.body)\n requestAnimationFrame(() => requestAnimationFrame(() => style.remove()))\n }\n}\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false\n return target.isContentEditable || target.closest(\"input, textarea, select, [contenteditable='true']\") !== null\n}\n\nfunction isTheme(value: string | null): value is WidgetTheme {\n return value !== null && (THEMES as readonly string[]).includes(value)\n}\n\nfunction readStoredTheme(): WidgetTheme | null {\n try {\n const value = localStorage.getItem(STORAGE_KEY)\n return isTheme(value) ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeStoredTheme(theme: WidgetTheme) {\n try {\n localStorage.setItem(STORAGE_KEY, theme)\n } catch {\n // Storage blocked (private mode, sandboxed iframe): the choice lasts for this page only.\n }\n}\n\nfunction noop() {}\n","import { StrictMode, Suspense, type ComponentType, type ReactNode } from \"react\"\nimport { createRoot } from \"react-dom/client\"\n\nimport { GristBoundary, GristWidgetProvider, useWidgetMetadata } from \"../sdk\"\nimport type { GristWidget, UseGristOptions } from \"../sdk\"\nimport { isGristWidget } from \"../sdk/define-widget\"\nimport { LocalDevPage as DefaultLocalDevPage } from \"./chrome/local-dev-page\"\nimport { ReleasePage as DefaultReleasePage } from \"./chrome/release-page\"\nimport { WidgetAlerts } from \"./chrome/widget-alerts\"\nimport { parseDeployPath, type DeployChannel } from \"./deploy-path\"\nimport { EmbeddedThemeProvider, StandaloneThemeProvider } from \"./widget-theme\"\n\nexport type GristWidgetAppProps = {\n /** The widget. A `React.lazy()` component works too: its code then loads only inside Grist. */\n App: ComponentType\n /** The widget's `GRIST_OPTIONS`: access level, columns, and so on. */\n options: UseGristOptions\n /** The page's title and description (the widget's `WIDGET_METADATA`, or its declaration's). */\n metadata?: { title?: string; description?: string }\n /**\n * Whether this is the local dev server. Pass `import.meta.env.DEV`.\n * Outside Grist it picks the local-dev page over the release page.\n */\n dev?: boolean\n /**\n * Wraps the widget inside Grist, under {@link GristBoundary}. The default\n * lists what needs the user's attention (column mapping, access level,\n * failed writes) above the widget; `null` renders the widget alone.\n */\n Alerts?: ComponentType<{ children: ReactNode }> | null\n /** Shown outside Grist on the local dev server. Defaults to a page pointing at `pnpm test` and the `dev` loop. */\n LocalDevPage?: ComponentType\n /**\n * Shown outside Grist on a deployed URL (`/latest/`, `/dev/`, `/v<version>/`).\n * Defaults to a page linking every channel and version, with the URL to\n * paste into Grist.\n */\n ReleasePage?: ComponentType<{ channel: DeployChannel }>\n}\n\nexport type MountGristWidgetConfig = GristWidgetAppProps & {\n /** The element to render into, or its CSS selector. Defaults to `#root`. */\n root?: Element | string\n}\n\n/**\n * A widget's entry module, `src/app/App.tsx`, as `import * as entry` gives\n * it: the widget as its default export, its declaration as `widget` (or, in\n * a widget made before `defineGristWidget`, `GRIST_OPTIONS` and\n * `WIDGET_METADATA`), and optionally its own `Alerts`, `LocalDevPage` and\n * `ReleasePage`. Everything a widget customises lives there, in its own\n * file: the template's `src/main.tsx` stays the same in every widget, and\n * `create-grist-widget upgrade` can rewrite it without losing anything.\n */\nexport type GristWidgetEntry = {\n default: ComponentType\n widget?: GristWidget\n GRIST_OPTIONS?: UseGristOptions\n WIDGET_METADATA?: { title?: string; description?: string }\n Alerts?: GristWidgetAppProps[\"Alerts\"]\n LocalDevPage?: GristWidgetAppProps[\"LocalDevPage\"]\n ReleasePage?: GristWidgetAppProps[\"ReleasePage\"]\n}\n\n/** The props {@link GristWidgetApp} takes, from a widget's entry module. */\nexport function entryProps(entry: GristWidgetEntry, dev = false): GristWidgetAppProps {\n const widget = isGristWidget(entry.widget) ? entry.widget : undefined\n const options = widget?.options ?? entry.GRIST_OPTIONS\n if (!options) {\n throw new Error(\n \"mountGristWidget: src/app/App.tsx exports neither `widget` (defineGristWidget) nor `GRIST_OPTIONS`.\"\n )\n }\n const props: GristWidgetAppProps = {\n App: entry.default,\n options,\n metadata: widget?.metadata ?? entry.WIDGET_METADATA,\n dev,\n }\n if (\"Alerts\" in entry) props.Alerts = entry.Alerts\n if (entry.LocalDevPage) props.LocalDevPage = entry.LocalDevPage\n if (entry.ReleasePage) props.ReleasePage = entry.ReleasePage\n return props\n}\n\nexport type MountedGristWidget = {\n unmount: () => void\n}\n\n/**\n * Render a widget into the page: everything `src/main.tsx` used to wire by\n * hand.\n *\n * ```tsx\n * import { mountGristWidget } from \"grist-widget-sdk/app\"\n * import \"./index.css\"\n * import * as entry from \"./app/App\"\n *\n * mountGristWidget(entry, { dev: import.meta.env.DEV })\n * ```\n *\n * It reads the entry module's exports ({@link GristWidgetEntry}): the widget,\n * its declaration and any page it replaces. The older form,\n * `mountGristWidget({ App, options: GRIST_OPTIONS, dev })`, still works.\n *\n * Inside Grist, the widget renders in {@link GristWidgetProvider} and\n * {@link GristBoundary}, which gates on its mapped columns when it declares\n * any, and follows Grist's light/dark theme. Outside Grist, where there is\n * nothing to connect to, the page shows `LocalDevPage` on the dev server and\n * `ReleasePage` on a deployed URL.\n */\nexport function mountGristWidget(entry: GristWidgetEntry, options?: { dev?: boolean; root?: Element | string }): MountedGristWidget\nexport function mountGristWidget(config: MountGristWidgetConfig): MountedGristWidget\nexport function mountGristWidget(\n config: MountGristWidgetConfig | GristWidgetEntry,\n entryOptions: { dev?: boolean; root?: Element | string } = {}\n): MountedGristWidget {\n const { root = \"#root\", ...props } =\n \"App\" in config ? config : { ...entryProps(config, entryOptions.dev), root: entryOptions.root }\n const container = typeof root === \"string\" ? document.querySelector(root) : root\n if (!container) {\n throw new Error(`mountGristWidget: no element matches \"${String(root)}\" to render the widget into.`)\n }\n\n const reactRoot = createRoot(container)\n reactRoot.render(\n <StrictMode>\n <GristWidgetApp {...props} />\n </StrictMode>\n )\n return { unmount: () => reactRoot.unmount() }\n}\n\n/**\n * The tree {@link mountGristWidget} renders, as a component: for a test, or\n * for a page that manages its own React root.\n */\nexport function GristWidgetApp({\n App,\n options,\n metadata,\n dev = false,\n Alerts = WidgetAlerts,\n LocalDevPage = DefaultLocalDevPage,\n ReleasePage = DefaultReleasePage,\n}: GristWidgetAppProps) {\n const Wrap = Alerts ?? PassThrough\n if (isEmbedded()) {\n return (\n <GristWidgetProvider options={options}>\n <PageMetadata metadata={metadata} />\n <EmbeddedThemeProvider>\n <GristBoundary>\n <div style={FILL}>\n <Wrap>\n <Suspense fallback={null}>\n <App />\n </Suspense>\n </Wrap>\n </div>\n </GristBoundary>\n </EmbeddedThemeProvider>\n </GristWidgetProvider>\n )\n }\n\n const channel = parseDeployPath(window.location.pathname).channel\n return (\n <StandaloneThemeProvider>\n <PageMetadata metadata={metadata} />\n <div style={FILL}>{dev ? <LocalDevPage /> : <ReleasePage channel={channel} />}</div>\n </StandaloneThemeProvider>\n )\n}\n\nconst FILL = { minHeight: \"100%\", width: \"100%\" } as const\n\n/** A Grist custom widget only ever runs in Grist's iframe. */\nfunction isEmbedded(): boolean {\n return typeof window !== \"undefined\" && window.self !== window.top\n}\n\n/** The page's title and description, from the widget's declaration. */\nfunction PageMetadata({ metadata }: { metadata: GristWidgetAppProps[\"metadata\"] }) {\n useWidgetMetadata(metadata?.title ? { title: metadata.title, description: metadata.description ?? \"\" } : null)\n return null\n}\n\nfunction PassThrough({ children }: { children: ReactNode }) {\n return <>{children}</>\n}\n","import { useEffect, useState } from \"react\"\n\nimport {\n cardStyle,\n cardTextStyle,\n cardTitleStyle,\n codeStyle,\n eyebrowStyle,\n headingStyle,\n leadStyle,\n pageStyle,\n} from \"./tokens\"\nimport { widgetTitle } from \"./widget-title\"\n\n/**\n * Whether the widget has a demo page (`public/demo/`). The dev server serves\n * it at `demo/index.html` (Vite answers a bare `demo/` with the app's own page).\n */\nfunction useHasDemo(): boolean | null {\n const [has, setHas] = useState<boolean | null>(null)\n useEffect(() => {\n if (typeof fetch !== \"function\") return\n let live = true\n fetch(\"demo/demo.grist\", { method: \"HEAD\" })\n .then((res) => live && setHas(res.ok))\n .catch(() => live && setHas(false))\n return () => {\n live = false\n }\n }, [])\n return has\n}\n\n/**\n * Shown outside Grist on the dev server. Deliberately not a preview of the\n * widget: an emulator can't match real Grist. It points at the loops worth\n * trusting: the widget in real Grist running in this browser (the demo page,\n * grist-static, on this dev server's build, so edits reload in place),\n * `pnpm test`, and the `dev` branch's preview inside a Grist document.\n */\nexport function LocalDevPage() {\n const hasDemo = useHasDemo()\n return (\n <main style={pageStyle}>\n <p style={eyebrowStyle}>{widgetTitle()} · local dev</p>\n <h1 style={headingStyle}>Not embedded in Grist</h1>\n <p style={leadStyle}>This is the local dev server: there's no Grist here to render the widget against.</p>\n\n <section style={cardStyle}>\n <h2 style={cardTitleStyle}>Open it in Grist, in this browser</h2>\n {hasDemo === false ? (\n <p style={cardTextStyle}>\n Run <code style={codeStyle}>pnpm demo --save</code> once (it needs Docker): it writes{\" \"}\n <code style={codeStyle}>public/demo/</code>, a document for this widget. Then{\" \"}\n <a href=\"demo/index.html\">demo/index.html</a> opens it in real Grist, running in the browser, on this dev server&apos;s\n widget: edits reload in place.\n </p>\n ) : (\n <p style={cardTextStyle}>\n <a href=\"demo/index.html\">Open demo/index.html</a>: real Grist running in the browser (no server, nothing saved), with\n this dev server&apos;s widget, so edits reload in place. Its document is{\" \"}\n <code style={codeStyle}>public/demo/demo.grist</code>: after changing the declared columns, run{\" \"}\n <code style={codeStyle}>pnpm demo --save</code> (Docker) to regenerate it.\n </p>\n )}\n </section>\n\n <section style={cardStyle}>\n <h2 style={cardTitleStyle}>Check it against real Grist</h2>\n <p style={cardTextStyle}>\n Push to <code style={codeStyle}>dev</code>, then open its <code style={codeStyle}>/dev/</code> URL in a\n custom widget of a real Grist document: later pushes reload it there within seconds. Locally,{\" \"}\n <code style={codeStyle}>pnpm test</code> runs the widget against an emulated Grist.\n </p>\n </section>\n\n <section style={cardStyle}>\n <h2 style={cardTitleStyle}>Starting from another example?</h2>\n <p style={cardTextStyle}>\n <code style={codeStyle}>npx create-grist-widget list</code> shows the examples, and{\" \"}\n <code style={codeStyle}>npx create-grist-widget use &lt;example&gt;</code> swaps this widget for one.\n </p>\n </section>\n </main>\n )\n}\n","import type { CSSProperties } from \"react\"\n\n/**\n * The chrome's colours and radius: shadcn's CSS variables, so the pages\n * follow a widget's theme (light, dark, its own palette) without depending\n * on its Tailwind config, with neutral fallbacks for a widget without them.\n * Same approach as GristBoundaryScreen.\n */\nconst token = (name: string, fallback: string) => `var(--${name}, ${fallback})`\n\nexport const tokens = {\n background: token(\"background\", \"#ffffff\"),\n foreground: token(\"foreground\", \"#0a0a0a\"),\n card: token(\"card\", \"#ffffff\"),\n mutedForeground: token(\"muted-foreground\", \"#737373\"),\n border: token(\"border\", \"#e5e5e5\"),\n primary: token(\"primary\", \"#171717\"),\n primaryForeground: token(\"primary-foreground\", \"#fafafa\"),\n destructive: token(\"destructive\", \"#dc2626\"),\n radius: token(\"radius\", \"0.625rem\"),\n} as const\n\nexport const pageStyle: CSSProperties = {\n boxSizing: \"border-box\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n maxWidth: \"28rem\",\n minHeight: \"100%\",\n margin: \"0 auto\",\n padding: \"4rem 1.5rem\",\n color: tokens.foreground,\n font: \"inherit\",\n}\n\nexport const eyebrowStyle: CSSProperties = {\n margin: \"0 0 0.5rem\",\n textAlign: \"center\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n color: tokens.mutedForeground,\n}\n\nexport const headingStyle: CSSProperties = {\n margin: 0,\n textAlign: \"center\",\n fontSize: \"1.5rem\",\n fontWeight: 600,\n letterSpacing: \"-0.01em\",\n}\n\nexport const leadStyle: CSSProperties = {\n margin: \"0.75rem auto 2rem\",\n maxWidth: \"24rem\",\n textAlign: \"center\",\n fontSize: \"0.875rem\",\n lineHeight: 1.5,\n color: tokens.mutedForeground,\n}\n\nexport const cardStyle: CSSProperties = {\n boxSizing: \"border-box\",\n marginBottom: \"1rem\",\n padding: \"1.25rem\",\n border: `1px solid ${tokens.border}`,\n borderRadius: tokens.radius,\n background: tokens.card,\n}\n\nexport const cardTitleStyle: CSSProperties = {\n margin: \"0 0 0.375rem\",\n fontSize: \"1rem\",\n fontWeight: 600,\n}\n\nexport const cardTextStyle: CSSProperties = {\n margin: 0,\n fontSize: \"0.875rem\",\n lineHeight: 1.5,\n color: tokens.mutedForeground,\n}\n\nexport const codeStyle: CSSProperties = {\n fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\",\n fontSize: \"0.8125rem\",\n color: tokens.foreground,\n}\n","/**\n * The widget's name for the pages shown outside Grist: the page title,\n * which `create-grist-widget` sets to the widget's title in `index.html`.\n */\nexport function widgetTitle(): string {\n const title = typeof document === \"undefined\" ? \"\" : document.title.trim()\n return title || \"Grist widget\"\n}\n","import { useState, type CSSProperties } from \"react\"\n\nimport { deployChannelLabel, parseDeployPath, type DeployChannel } from \"../deploy-path\"\nimport { useReleasedVersions } from \"../released-versions\"\nimport {\n cardStyle,\n cardTextStyle,\n cardTitleStyle,\n codeStyle,\n eyebrowStyle,\n headingStyle,\n leadStyle,\n pageStyle,\n tokens,\n} from \"./tokens\"\nimport { widgetTitle } from \"./widget-title\"\n\n/**\n * Shown outside Grist on a deployed URL (`/latest/`, `/dev/`, `/v<version>/`\n * or the root): which build this is, links to the others, and the URL to\n * paste into a Grist custom widget.\n */\nexport function ReleasePage({ channel }: { channel: DeployChannel }) {\n const label = deployChannelLabel(channel)\n return (\n <main style={pageStyle}>\n <p style={eyebrowStyle}>\n {widgetTitle()} · {label}\n </p>\n <h1 style={headingStyle}>Grist isn't loaded here</h1>\n <p style={leadStyle}>\n This is the <code style={codeStyle}>{label}</code> build, opened outside Grist. Hover a version to see when it\n was released.\n </p>\n <ChannelLinks current={channel} />\n <InstallUrl />\n </main>\n )\n}\n\nfunction ChannelLinks({ current }: { current: DeployChannel }) {\n const { basePath } = parseDeployPath(window.location.pathname)\n const { versions } = useReleasedVersions()\n const isCurrent = (channel: DeployChannel) =>\n channel.kind === current.kind && (channel.kind !== \"version\" || (current.kind === \"version\" && channel.version === current.version))\n\n return (\n <nav aria-label=\"Versions\" style={{ display: \"flex\", flexWrap: \"wrap\", justifyContent: \"center\", gap: \"0.5rem\", marginBottom: \"2rem\" }}>\n <ChannelLink href={`${basePath}latest/`} active={isCurrent({ kind: \"latest\" })}>\n latest\n </ChannelLink>\n <ChannelLink href={`${basePath}dev/`} active={isCurrent({ kind: \"dev\" })}>\n dev\n </ChannelLink>\n {versions?.map((v) => (\n <ChannelLink\n key={v.version}\n href={`${basePath}v${v.version}/`}\n title={formatDate(v.publishedAt)}\n active={isCurrent({ kind: \"version\", version: v.version })}\n >\n {`v${v.version}`}\n </ChannelLink>\n ))}\n </nav>\n )\n}\n\nfunction ChannelLink({\n href,\n title,\n active,\n children,\n}: {\n href: string\n title?: string\n active: boolean\n children: string\n}) {\n const style: CSSProperties = {\n padding: \"0.25rem 0.75rem\",\n border: `1px solid ${active ? tokens.primary : tokens.border}`,\n borderRadius: \"9999px\",\n background: active ? tokens.primary : \"transparent\",\n color: active ? tokens.primaryForeground : tokens.mutedForeground,\n fontSize: \"0.75rem\",\n fontWeight: 500,\n textDecoration: \"none\",\n }\n return (\n <a href={href} title={title} aria-current={active ? \"page\" : undefined} style={style}>\n {children}\n </a>\n )\n}\n\nfunction InstallUrl() {\n const url = window.location.href\n const [copied, setCopied] = useState(false)\n\n function copy() {\n void navigator.clipboard?.writeText(url).then(() => {\n setCopied(true)\n setTimeout(() => setCopied(false), 1500)\n })\n }\n\n return (\n <section style={cardStyle}>\n <h2 style={cardTitleStyle}>Use it as a Grist custom widget</h2>\n <p style={{ ...cardTextStyle, marginBottom: \"1rem\" }}>Paste this URL into a custom widget's URL field in Grist.</p>\n <div style={{ display: \"flex\", gap: \"0.5rem\" }}>\n <input\n readOnly\n value={url}\n aria-label=\"Widget URL\"\n onFocus={(event) => event.target.select()}\n style={{\n flex: 1,\n minWidth: 0,\n padding: \"0.5rem 0.75rem\",\n border: `1px solid ${tokens.border}`,\n borderRadius: tokens.radius,\n background: \"transparent\",\n color: tokens.foreground,\n font: \"inherit\",\n fontSize: \"0.875rem\",\n }}\n />\n <button\n type=\"button\"\n onClick={copy}\n style={{\n padding: \"0.5rem 1rem\",\n border: \"none\",\n borderRadius: tokens.radius,\n background: tokens.primary,\n color: tokens.primaryForeground,\n font: \"inherit\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n cursor: \"pointer\",\n }}\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n </section>\n )\n}\n\nfunction formatDate(iso: string): string {\n const date = new Date(iso)\n return Number.isNaN(date.getTime())\n ? iso\n : date.toLocaleDateString(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" })\n}\n","/**\n * The alerts `mountGristWidget()` shows above a running widget: what needs the\n * user's attention in Grist (column mapping, access, a failed write, an\n * unlinked section). The blocking states (not connected, access refused,\n * required columns unmapped) are `<GristBoundary>`'s, not alerts.\n */\n\nimport { useNearestGristState } from \"../use-grist\"\nimport { deriveColumnMappingStatus, deriveStatus } from \"../runtime/derive\"\nimport { isAccessInsufficientError } from \"../runtime/api\"\nimport type { GristCellIssue } from \"../runtime/cells\"\nimport type { GristRuntimeState } from \"../runtime/runtime\"\nimport type { GristColumnsToMap, GristWidgetInteraction } from \"../types\"\nimport { parseGristWidgetLinkingInfo } from \"./grist-widget-interaction\"\n\nexport type GristSdkAlertKind =\n | \"column-mapping\"\n | \"mapping-pending\"\n | \"section-not-linked\"\n | \"source-not-wired\"\n | \"action-error\"\n | \"access-insufficient\"\n | \"map-back-skip\"\n | \"cell-issues\"\n\nexport type GristSdkAlertSeverity = \"info\" | \"warning\" | \"error\"\n\nexport type GristSdkAlertDescriptor = {\n id: string\n kind: GristSdkAlertKind\n severity: GristSdkAlertSeverity\n title: string\n ariaRole: \"status\" | \"alert\"\n message: string\n}\n\nexport type GristSdkAlertsOptions = {\n /** Added to the \"section not linked\" message, e.g. how to link in Grist. */\n sectionNotLinkedHint?: string\n}\n\nconst TITLE: Record<GristSdkAlertKind, string> = {\n \"column-mapping\": \"Column mapping\",\n \"mapping-pending\": \"Column mapping\",\n \"section-not-linked\": \"Widget linking\",\n \"source-not-wired\": \"Widget linking\",\n \"action-error\": \"Action error\",\n \"access-insufficient\": \"Access level\",\n \"map-back-skip\": \"Write skipped\",\n \"cell-issues\": \"Unexpected values\",\n}\n\nconst SEVERITY: Record<GristSdkAlertKind, GristSdkAlertSeverity> = {\n \"column-mapping\": \"warning\",\n \"mapping-pending\": \"info\",\n \"section-not-linked\": \"warning\",\n \"source-not-wired\": \"warning\",\n \"action-error\": \"error\",\n \"access-insufficient\": \"error\",\n \"map-back-skip\": \"warning\",\n \"cell-issues\": \"warning\",\n}\n\nconst alert = (kind: GristSdkAlertKind, message: string, ariaRole: \"status\" | \"alert\" = \"status\"): GristSdkAlertDescriptor => ({\n id: kind,\n kind,\n severity: SEVERITY[kind],\n title: TITLE[kind],\n ariaRole,\n message,\n})\n\n/** A widget section Grist says is linked to nothing. */\nexport function isWidgetSectionNotLinked(interaction: GristWidgetInteraction | null | undefined): boolean {\n const linking = parseGristWidgetLinkingInfo(interaction?.linking)\n return Boolean(linking) && linking!.asTarget == null\n}\n\n/** The alerts for a widget in `state`, pure (tested without React). */\nexport function getGristSdkAlerts(\n state: GristRuntimeState,\n widget: {\n columns?: GristColumnsToMap\n allowSelectBy?: boolean\n suppressAlerts?: readonly GristSdkAlertKind[]\n cellIssues?: readonly GristCellIssue[]\n },\n options: GristSdkAlertsOptions = {}\n): GristSdkAlertDescriptor[] {\n if (deriveStatus(state) !== \"ready\") return []\n const out: GristSdkAlertDescriptor[] = []\n\n const mapping = deriveColumnMappingStatus(widget.columns, state.mappings)\n if (mapping.pending) out.push(alert(\"mapping-pending\", \"Waiting for Grist to report column mappings.\"))\n else if (!mapping.ok) {\n const parts = [\"Column mapping is incomplete.\"]\n if (mapping.missing.length) parts.push(`Required columns: ${mapping.missing.join(\", \")}.`)\n if (mapping.emptyMultiples.length) parts.push(`Assign at least one sheet column for: ${mapping.emptyMultiples.join(\", \")}.`)\n out.push(alert(\"column-mapping\", parts.join(\" \")))\n }\n\n const failure = state.actionError ?? state.readError\n if (failure && isAccessInsufficientError(failure)) {\n out.push(\n alert(\n \"access-insufficient\",\n 'This operation requires \"Full document access\". Set the Access level to \"Full document access\" in the Custom widget configuration panel.',\n \"alert\"\n )\n )\n } else if (state.actionError) {\n out.push(alert(\"action-error\", state.actionError, \"alert\"))\n }\n\n if (state.mapBackSkipped.length) {\n const list = state.mapBackSkipped.join(\", \")\n out.push(alert(\"map-back-skip\", `mapBack skipped multi-mapped column${state.mapBackSkipped.length === 1 ? \"\" : \"s\"}: ${list}.`))\n }\n\n const cellIssues = widget.cellIssues ?? []\n if (cellIssues.length) {\n const first = cellIssues[0]!\n const where = first.column ? `${first.column} in row ${first.rowId}` : `row ${first.rowId}`\n const more = cellIssues.length > 1 ? ` (and ${cellIssues.length - 1} more)` : \"\"\n out.push(alert(\"cell-issues\", `${where}: ${first.message}${more}. Check the column's type, or the cell, in Grist.`))\n }\n\n const linking = parseGristWidgetLinkingInfo(state.interaction?.linking)\n if (widget.allowSelectBy) {\n if (linking && !linking.asSource) {\n out.push(\n alert(\n \"source-not-wired\",\n \"This widget is configured as a selector (allowSelectBy) but no other section is using it as a link source yet. Link another section to read from this widget.\"\n )\n )\n }\n } else if (isWidgetSectionNotLinked(state.interaction)) {\n const hint = options.sectionNotLinkedHint?.trim()\n out.push(\n alert(\n \"section-not-linked\",\n \"This widget section is not linked to a table or selector. Link it from the page settings so the selected row is sent to the widget.\" +\n (hint ? ` ${hint}` : \"\")\n )\n )\n }\n\n const suppressed = widget.suppressAlerts ?? []\n return out.filter((a) => !suppressed.includes(a.kind))\n}\n\n/** @internal The alerts for the widget under the nearest `<GristWidgetProvider>`; [] outside one. */\nexport function useGristSdkAlerts(options?: GristSdkAlertsOptions): GristSdkAlertDescriptor[] {\n const { connection, state, cellIssues } = useNearestGristState()\n if (!connection) return []\n const { columns, allowSelectBy, suppressAlerts } = connection.runtime.options\n return getGristSdkAlerts(state, { columns, allowSelectBy, suppressAlerts, cellIssues }, options)\n}\n","import type { CSSProperties, ReactNode } from \"react\"\n\nimport { useGristSdkAlerts, type GristSdkAlertDescriptor, type GristSdkAlertsOptions } from \"../../sdk/helpers/grist-sdk-alerts\"\nimport { tokens } from \"./tokens\"\n\nconst ALERT_OPTIONS: GristSdkAlertsOptions = {\n sectionNotLinkedHint:\n \"In Grist, select this widget section on the page, then use Link to connect a table, card, or chart view as the selector.\",\n}\n\n/**\n * The default `Alerts` of {@link mountGristWidget}: what needs the user's\n * attention (column mapping, access level, failed writes, an unlinked\n * section), above the widget. Renders nothing extra when all is well.\n */\nexport function WidgetAlerts({ children }: { children: ReactNode }) {\n const alerts = useGristSdkAlerts(ALERT_OPTIONS)\n if (alerts.length === 0) return <>{children}</>\n\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.75rem\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.75rem\", padding: \"0.75rem 0.75rem 0\" }}>\n {alerts.map((alert) => (\n <WidgetAlert key={alert.id} alert={alert} />\n ))}\n </div>\n {children}\n </div>\n )\n}\n\nfunction WidgetAlert({ alert }: { alert: GristSdkAlertDescriptor }) {\n const linkIcon = alert.kind === \"section-not-linked\" || alert.kind === \"source-not-wired\"\n return (\n <div role={alert.ariaRole} style={{ ...alertStyle, ...SEVERITY[alert.severity] }}>\n <span aria-hidden style={{ flexShrink: 0, marginTop: \"0.125rem\" }}>\n {linkIcon ? <LinkIcon /> : <WarningIcon />}\n </span>\n <div style={{ minWidth: 0 }}>\n <p style={{ margin: 0, fontWeight: 500 }}>{alert.title}</p>\n <p style={{ margin: \"0.25rem 0 0\", color: tokens.mutedForeground }}>{alert.message}</p>\n </div>\n </div>\n )\n}\n\nconst alertStyle: CSSProperties = {\n boxSizing: \"border-box\",\n display: \"flex\",\n gap: \"0.75rem\",\n padding: \"0.75rem 1rem\",\n border: `1px solid ${tokens.border}`,\n borderRadius: tokens.radius,\n color: tokens.foreground,\n fontSize: \"0.875rem\",\n lineHeight: 1.5,\n}\n\nconst SEVERITY: Record<GristSdkAlertDescriptor[\"severity\"], CSSProperties> = {\n error: { borderColor: tokens.destructive, color: tokens.destructive },\n warning: { borderColor: \"rgb(245 158 11 / 0.4)\", background: \"rgb(245 158 11 / 0.1)\" },\n info: { background: \"transparent\" },\n}\n\nconst ICON = {\n width: 16,\n height: 16,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n} as const\n\nfunction WarningIcon() {\n return (\n <svg {...ICON}>\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n </svg>\n )\n}\n\nfunction LinkIcon() {\n return (\n <svg {...ICON}>\n <path d=\"M9 17H7A5 5 0 0 1 7 7h2\" />\n <path d=\"M15 7h2a5 5 0 1 1 0 10h-2\" />\n <line x1=\"8\" x2=\"16\" y1=\"12\" y2=\"12\" />\n </svg>\n )\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,IAAM,kBAAkB;AAOjB,SAAS,gBAAgB,UAA8B;AAC5D,QAAM,WAAW,SAAS,QAAQ,QAAQ,EAAE,EAAE,MAAM,GAAG;AACvD,QAAM,OAAO,SAAS,SAAS,SAAS,CAAC,KAAK;AAE9C,MAAI,UAAgC;AACpC,MAAI,SAAS,SAAU,WAAU,EAAE,MAAM,SAAS;AAAA,WACzC,SAAS,MAAO,WAAU,EAAE,MAAM,MAAM;AAAA,OAC5C;AACH,UAAM,QAAQ,gBAAgB,KAAK,IAAI;AACvC,QAAI,MAAO,WAAU,EAAE,MAAM,WAAW,SAAS,MAAM,CAAC,EAAE;AAAA,EAC5D;AAEA,MAAI,CAAC,QAAS,QAAO,EAAE,SAAS,EAAE,MAAM,SAAS,GAAG,UAAU,kBAAkB,QAAQ,EAAE;AAC1F,SAAO,EAAE,SAAS,UAAU,kBAAkB,SAAS,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE;AACjF;AAGO,SAAS,mBAAmB,SAAgC;AACjE,SAAO,QAAQ,SAAS,YAAY,IAAI,QAAQ,OAAO,KAAK,QAAQ;AACtE;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,KAAK,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI;AAC5C;;;ACrDA,SAAS,WAAW,gBAAgB;AAsB7B,SAAS,oBACd,WAAmB,OAAO,WAAW,cAAc,MAAM,OAAO,SAAS,UAC9C;AAC3B,QAAM,MAAM,GAAG,gBAAgB,QAAQ,EAAE,QAAQ;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAoC,EAAE,UAAU,MAAM,OAAO,MAAM,CAAC;AAEhG,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,UAAM,KAAK,EAAE,OAAO,WAAW,CAAC,EAC7B,KAAK,CAAC,QAAS,IAAI,KAAK,IAAI,KAAK,IAAI,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC,CAAE,EACrF,KAAK,CAAC,SAAkB;AACvB,UAAI,UAAW;AACf,UAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,+BAA+B;AACzE,gBAAU,EAAE,UAAU,MAA2B,OAAO,MAAM,CAAC;AAAA,IACjE,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,UAAW,WAAU,EAAE,UAAU,MAAM,OAAO,KAAK,CAAC;AAAA,IAC3D,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AAER,SAAO;AACT;;;AC9CA,SAAS,eAAe,aAAa,YAAY,aAAAA,YAAW,SAAS,YAAAC,iBAAgC;AAoD5F;AA9BT,IAAM,cAAc;AACpB,IAAM,aAAa;AACnB,IAAM,SAAiC,CAAC,SAAS,QAAQ,QAAQ;AAEjE,IAAM,eAAe,cAAqC,IAAI;AASvD,SAAS,WAA2B;AACzC,QAAM,QAAQ,WAAW,YAAY;AACrC,SAAO,SAAS,EAAE,OAAO,UAAU,eAAe,YAAY,GAAG,UAAU,KAAK;AAClF;AAGO,SAAS,sBAAsB,EAAE,SAAS,GAA4B;AAC3E,QAAM,aAAa,qBAAqB,EAAE,MAAM;AAChD,QAAM,SAAS,eAAe,eAAe,IAAI;AACjD,QAAM,gBAAgB,cAAc;AAEpC,gBAAc,aAAa;AAE3B,QAAM,QAAQ;AAAA,IACZ,OAAO,EAAE,OAAO,cAAc,UAAU,eAAe,UAAU,KAAK;AAAA,IACtE,CAAC,YAAY,aAAa;AAAA,EAC5B;AACA,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AACxD;AAOO,SAAS,wBAAwB,EAAE,SAAS,GAA4B;AAC7E,QAAM,CAAC,OAAO,aAAa,IAAIC,UAAsB,MAAM,gBAAgB,KAAK,QAAQ;AACxF,QAAM,SAAS,eAAe,UAAU,QAAQ;AAChD,QAAM,gBAAgB,UAAU,WAAW,SAAS;AAEpD,gBAAc,aAAa;AAE3B,QAAM,WAAW,YAAY,CAAC,SAAsB;AAClD,qBAAiB,IAAI;AACrB,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,CAAC;AAGL,EAAAC,WAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAAyB;AAC1C,UAAI,MAAM,UAAU,MAAM,WAAW,MAAM,WAAW,MAAM,OAAQ;AACpE,UAAI,MAAM,IAAI,YAAY,MAAM,OAAO,iBAAiB,MAAM,MAAM,EAAG;AACvE,oBAAc,CAAC,YAAY;AACzB,cAAM,kBAAkB,YAAY,WAAW,YAAY,IAAI;AAC/D,cAAM,OAAoB,oBAAoB,SAAS,UAAU;AACjE,yBAAiB,IAAI;AACrB,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,CAAC;AAGL,EAAAA,WAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAAwB;AACzC,UAAI,MAAM,QAAQ,YAAa;AAC/B,oBAAc,QAAQ,MAAM,QAAQ,IAAI,MAAM,WAAW,QAAQ;AAAA,IACnE;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ;AAAA,IACZ,OAAO,EAAE,OAAO,eAAe,SAAS;AAAA,IACxC,CAAC,OAAO,eAAe,QAAQ;AAAA,EACjC;AACA,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AACxD;AAGA,SAAS,eAAe,QAAsC;AAC5D,QAAM,CAAC,OAAO,QAAQ,IAAID,UAA8B,WAAW;AAEnE,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,OAAO,eAAe,WAAY;AACxD,UAAM,QAAQ,OAAO,WAAW,UAAU;AAC1C,UAAM,WAAW,MAAM,SAAS,MAAM,UAAU,SAAS,OAAO;AAChE,aAAS;AACT,UAAM,iBAAiB,UAAU,QAAQ;AACzC,WAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAAA,EAC3D,GAAG,CAAC,MAAM,CAAC;AAEX,SAAO;AACT;AAGA,SAAS,cAAc,OAA4B;AACjD,EAAAA,WAAU,MAAM;AACd,UAAM,OAAO,SAAS;AACtB,QAAI,KAAK,UAAU,SAAS,KAAK,KAAK,CAAC,KAAK,UAAU,SAAS,MAAM,KAAK,CAAC,EAAG;AAC9E,UAAM,qBAAqB,mBAAmB;AAC9C,SAAK,UAAU,OAAO,MAAM,KAAK,CAAC;AAClC,SAAK,UAAU,IAAI,KAAK;AACxB,uBAAmB;AAAA,EACrB,GAAG,CAAC,KAAK,CAAC;AACZ;AAEA,SAAS,cAAmC;AAC1C,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,WAAY,QAAO;AACrF,SAAO,OAAO,WAAW,UAAU,EAAE,UAAU,SAAS;AAC1D;AAEA,SAAS,MAAM,OAAiD;AAC9D,SAAO,UAAU,SAAS,UAAU;AACtC;AAGA,SAAS,qBAAiC;AACxC,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM;AAAA,IACJ,SAAS,eAAe,mFAAmF;AAAA,EAC7G;AACA,WAAS,KAAK,YAAY,KAAK;AAC/B,SAAO,MAAM;AACX,WAAO,iBAAiB,SAAS,IAAI;AACrC,0BAAsB,MAAM,sBAAsB,MAAM,MAAM,OAAO,CAAC,CAAC;AAAA,EACzE;AACF;AAEA,SAAS,iBAAiB,QAAqC;AAC7D,MAAI,EAAE,kBAAkB,aAAc,QAAO;AAC7C,SAAO,OAAO,qBAAqB,OAAO,QAAQ,mDAAmD,MAAM;AAC7G;AAEA,SAAS,QAAQ,OAA4C;AAC3D,SAAO,UAAU,QAAS,OAA6B,SAAS,KAAK;AACvE;AAEA,SAAS,kBAAsC;AAC7C,MAAI;AACF,UAAM,QAAQ,aAAa,QAAQ,WAAW;AAC9C,WAAO,QAAQ,KAAK,IAAI,QAAQ;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,OAAoB;AAC5C,MAAI;AACF,iBAAa,QAAQ,aAAa,KAAK;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,OAAO;AAAC;;;ACrLjB,SAAS,YAAY,gBAAoD;AACzE,SAAS,kBAAkB;;;ACD3B,SAAS,aAAAC,YAAW,YAAAC,iBAAgB;;;ACQpC,IAAM,QAAQ,CAAC,MAAc,aAAqB,SAAS,IAAI,KAAK,QAAQ;AAErE,IAAM,SAAS;AAAA,EACpB,YAAY,MAAM,cAAc,SAAS;AAAA,EACzC,YAAY,MAAM,cAAc,SAAS;AAAA,EACzC,MAAM,MAAM,QAAQ,SAAS;AAAA,EAC7B,iBAAiB,MAAM,oBAAoB,SAAS;AAAA,EACpD,QAAQ,MAAM,UAAU,SAAS;AAAA,EACjC,SAAS,MAAM,WAAW,SAAS;AAAA,EACnC,mBAAmB,MAAM,sBAAsB,SAAS;AAAA,EACxD,aAAa,MAAM,eAAe,SAAS;AAAA,EAC3C,QAAQ,MAAM,UAAU,UAAU;AACpC;AAEO,IAAM,YAA2B;AAAA,EACtC,WAAW;AAAA,EACX,SAAS;AAAA,EACT,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO,OAAO;AAAA,EACd,MAAM;AACR;AAEO,IAAM,eAA8B;AAAA,EACzC,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO,OAAO;AAChB;AAEO,IAAM,eAA8B;AAAA,EACzC,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,eAAe;AACjB;AAEO,IAAM,YAA2B;AAAA,EACtC,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO,OAAO;AAChB;AAEO,IAAM,YAA2B;AAAA,EACtC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,SAAS;AAAA,EACT,QAAQ,aAAa,OAAO,MAAM;AAAA,EAClC,cAAc,OAAO;AAAA,EACrB,YAAY,OAAO;AACrB;AAEO,IAAM,iBAAgC;AAAA,EAC3C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,YAAY;AACd;AAEO,IAAM,gBAA+B;AAAA,EAC1C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO,OAAO;AAChB;AAEO,IAAM,YAA2B;AAAA,EACtC,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,OAAO,OAAO;AAChB;;;AClFO,SAAS,cAAsB;AACpC,QAAM,QAAQ,OAAO,aAAa,cAAc,KAAK,SAAS,MAAM,KAAK;AACzE,SAAO,SAAS;AAClB;;;AFqCM,SACA,OAAAC,MADA;AA1BN,SAAS,aAA6B;AACpC,QAAM,CAAC,KAAK,MAAM,IAAIC,UAAyB,IAAI;AACnD,EAAAC,WAAU,MAAM;AACd,QAAI,OAAO,UAAU,WAAY;AACjC,QAAI,OAAO;AACX,UAAM,mBAAmB,EAAE,QAAQ,OAAO,CAAC,EACxC,KAAK,CAAC,QAAQ,QAAQ,OAAO,IAAI,EAAE,CAAC,EACpC,MAAM,MAAM,QAAQ,OAAO,KAAK,CAAC;AACpC,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,CAAC;AACL,SAAO;AACT;AASO,SAAS,eAAe;AAC7B,QAAM,UAAU,WAAW;AAC3B,SACE,qBAAC,UAAK,OAAO,WACX;AAAA,yBAAC,OAAE,OAAO,cAAe;AAAA,kBAAY;AAAA,MAAE;AAAA,OAAY;AAAA,IACnD,gBAAAF,KAAC,QAAG,OAAO,cAAc,mCAAqB;AAAA,IAC9C,gBAAAA,KAAC,OAAE,OAAO,WAAW,+FAAiF;AAAA,IAEtG,qBAAC,aAAQ,OAAO,WACd;AAAA,sBAAAA,KAAC,QAAG,OAAO,gBAAgB,+CAAiC;AAAA,MAC3D,YAAY,QACX,qBAAC,OAAE,OAAO,eAAe;AAAA;AAAA,QACnB,gBAAAA,KAAC,UAAK,OAAO,WAAW,8BAAgB;AAAA,QAAO;AAAA,QAAmC;AAAA,QACtF,gBAAAA,KAAC,UAAK,OAAO,WAAW,0BAAY;AAAA,QAAO;AAAA,QAAmC;AAAA,QAC9E,gBAAAA,KAAC,OAAE,MAAK,mBAAkB,6BAAe;AAAA,QAAI;AAAA,SAE/C,IAEA,qBAAC,OAAE,OAAO,eACR;AAAA,wBAAAA,KAAC,OAAE,MAAK,mBAAkB,kCAAoB;AAAA,QAAI;AAAA,QACuB;AAAA,QACzE,gBAAAA,KAAC,UAAK,OAAO,WAAW,oCAAsB;AAAA,QAAO;AAAA,QAA2C;AAAA,QAChG,gBAAAA,KAAC,UAAK,OAAO,WAAW,8BAAgB;AAAA,QAAO;AAAA,SACjD;AAAA,OAEJ;AAAA,IAEA,qBAAC,aAAQ,OAAO,WACd;AAAA,sBAAAA,KAAC,QAAG,OAAO,gBAAgB,yCAA2B;AAAA,MACtD,qBAAC,OAAE,OAAO,eAAe;AAAA;AAAA,QACf,gBAAAA,KAAC,UAAK,OAAO,WAAW,iBAAG;AAAA,QAAO;AAAA,QAAgB,gBAAAA,KAAC,UAAK,OAAO,WAAW,mBAAK;AAAA,QAAO;AAAA,QACA;AAAA,QAC9F,gBAAAA,KAAC,UAAK,OAAO,WAAW,uBAAS;AAAA,QAAO;AAAA,SAC1C;AAAA,OACF;AAAA,IAEA,qBAAC,aAAQ,OAAO,WACd;AAAA,sBAAAA,KAAC,QAAG,OAAO,gBAAgB,4CAA8B;AAAA,MACzD,qBAAC,OAAE,OAAO,eACR;AAAA,wBAAAA,KAAC,UAAK,OAAO,WAAW,0CAA4B;AAAA,QAAO;AAAA,QAAyB;AAAA,QACpF,gBAAAA,KAAC,UAAK,OAAO,WAAW,mDAA2C;AAAA,QAAO;AAAA,SAC5E;AAAA,OACF;AAAA,KACF;AAEJ;;;AGrFA,SAAS,YAAAG,iBAAoC;AA0BvC,SAGA,OAAAC,MAHA,QAAAC,aAAA;AAJC,SAAS,YAAY,EAAE,QAAQ,GAA+B;AACnE,QAAM,QAAQ,mBAAmB,OAAO;AACxC,SACE,gBAAAA,MAAC,UAAK,OAAO,WACX;AAAA,oBAAAA,MAAC,OAAE,OAAO,cACP;AAAA,kBAAY;AAAA,MAAE;AAAA,MAAI;AAAA,OACrB;AAAA,IACA,gBAAAD,KAAC,QAAG,OAAO,cAAc,qCAAuB;AAAA,IAChD,gBAAAC,MAAC,OAAE,OAAO,WAAW;AAAA;AAAA,MACP,gBAAAD,KAAC,UAAK,OAAO,WAAY,iBAAM;AAAA,MAAO;AAAA,OAEpD;AAAA,IACA,gBAAAA,KAAC,gBAAa,SAAS,SAAS;AAAA,IAChC,gBAAAA,KAAC,cAAW;AAAA,KACd;AAEJ;AAEA,SAAS,aAAa,EAAE,QAAQ,GAA+B;AAC7D,QAAM,EAAE,SAAS,IAAI,gBAAgB,OAAO,SAAS,QAAQ;AAC7D,QAAM,EAAE,SAAS,IAAI,oBAAoB;AACzC,QAAM,YAAY,CAAC,YACjB,QAAQ,SAAS,QAAQ,SAAS,QAAQ,SAAS,aAAc,QAAQ,SAAS,aAAa,QAAQ,YAAY,QAAQ;AAE7H,SACE,gBAAAC,MAAC,SAAI,cAAW,YAAW,OAAO,EAAE,SAAS,QAAQ,UAAU,QAAQ,gBAAgB,UAAU,KAAK,UAAU,cAAc,OAAO,GACnI;AAAA,oBAAAD,KAAC,eAAY,MAAM,GAAG,QAAQ,WAAW,QAAQ,UAAU,EAAE,MAAM,SAAS,CAAC,GAAG,oBAEhF;AAAA,IACA,gBAAAA,KAAC,eAAY,MAAM,GAAG,QAAQ,QAAQ,QAAQ,UAAU,EAAE,MAAM,MAAM,CAAC,GAAG,iBAE1E;AAAA,IACC,UAAU,IAAI,CAAC,MACd,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM,GAAG,QAAQ,IAAI,EAAE,OAAO;AAAA,QAC9B,OAAO,WAAW,EAAE,WAAW;AAAA,QAC/B,QAAQ,UAAU,EAAE,MAAM,WAAW,SAAS,EAAE,QAAQ,CAAC;AAAA,QAExD,cAAI,EAAE,OAAO;AAAA;AAAA,MALT,EAAE;AAAA,IAMT,CACD;AAAA,KACH;AAEJ;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,QAAuB;AAAA,IAC3B,SAAS;AAAA,IACT,QAAQ,aAAa,SAAS,OAAO,UAAU,OAAO,MAAM;AAAA,IAC5D,cAAc;AAAA,IACd,YAAY,SAAS,OAAO,UAAU;AAAA,IACtC,OAAO,SAAS,OAAO,oBAAoB,OAAO;AAAA,IAClD,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAClB;AACA,SACE,gBAAAA,KAAC,OAAE,MAAY,OAAc,gBAAc,SAAS,SAAS,QAAW,OACrE,UACH;AAEJ;AAEA,SAAS,aAAa;AACpB,QAAM,MAAM,OAAO,SAAS;AAC5B,QAAM,CAAC,QAAQ,SAAS,IAAIE,UAAS,KAAK;AAE1C,WAAS,OAAO;AACd,SAAK,UAAU,WAAW,UAAU,GAAG,EAAE,KAAK,MAAM;AAClD,gBAAU,IAAI;AACd,iBAAW,MAAM,UAAU,KAAK,GAAG,IAAI;AAAA,IACzC,CAAC;AAAA,EACH;AAEA,SACE,gBAAAD,MAAC,aAAQ,OAAO,WACd;AAAA,oBAAAD,KAAC,QAAG,OAAO,gBAAgB,6CAA+B;AAAA,IAC1D,gBAAAA,KAAC,OAAE,OAAO,EAAE,GAAG,eAAe,cAAc,OAAO,GAAG,uEAAyD;AAAA,IAC/G,gBAAAC,MAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,SAAS,GAC3C;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,UAAQ;AAAA,UACR,OAAO;AAAA,UACP,cAAW;AAAA,UACX,SAAS,CAAC,UAAU,MAAM,OAAO,OAAO;AAAA,UACxC,OAAO;AAAA,YACL,MAAM;AAAA,YACN,UAAU;AAAA,YACV,SAAS;AAAA,YACT,QAAQ,aAAa,OAAO,MAAM;AAAA,YAClC,cAAc,OAAO;AAAA,YACrB,YAAY;AAAA,YACZ,OAAO,OAAO;AAAA,YACd,MAAM;AAAA,YACN,UAAU;AAAA,UACZ;AAAA;AAAA,MACF;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,YACL,SAAS;AAAA,YACT,QAAQ;AAAA,YACR,cAAc,OAAO;AAAA,YACrB,YAAY,OAAO;AAAA,YACnB,OAAO,OAAO;AAAA,YACd,MAAM;AAAA,YACN,UAAU;AAAA,YACV,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UAEC,mBAAS,WAAW;AAAA;AAAA,MACvB;AAAA,OACF;AAAA,KACF;AAEJ;AAEA,SAAS,WAAW,KAAqB;AACvC,QAAM,OAAO,IAAI,KAAK,GAAG;AACzB,SAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAC9B,MACA,KAAK,mBAAmB,QAAW,EAAE,MAAM,WAAW,OAAO,SAAS,KAAK,UAAU,CAAC;AAC5F;;;ACnHA,IAAM,QAA2C;AAAA,EAC/C,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEA,IAAM,WAA6D;AAAA,EACjE,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEA,IAAM,QAAQ,CAAC,MAAyB,SAAiB,WAA+B,cAAuC;AAAA,EAC7H,IAAI;AAAA,EACJ;AAAA,EACA,UAAU,SAAS,IAAI;AAAA,EACvB,OAAO,MAAM,IAAI;AAAA,EACjB;AAAA,EACA;AACF;AAGO,SAAS,yBAAyB,aAAiE;AACxG,QAAM,UAAU,4BAA4B,aAAa,OAAO;AAChE,SAAO,QAAQ,OAAO,KAAK,QAAS,YAAY;AAClD;AAGO,SAAS,kBACd,OACA,QAMA,UAAiC,CAAC,GACP;AAC3B,MAAI,aAAa,KAAK,MAAM,QAAS,QAAO,CAAC;AAC7C,QAAM,MAAiC,CAAC;AAExC,QAAM,UAAU,0BAA0B,OAAO,SAAS,MAAM,QAAQ;AACxE,MAAI,QAAQ,QAAS,KAAI,KAAK,MAAM,mBAAmB,8CAA8C,CAAC;AAAA,WAC7F,CAAC,QAAQ,IAAI;AACpB,UAAM,QAAQ,CAAC,+BAA+B;AAC9C,QAAI,QAAQ,QAAQ,OAAQ,OAAM,KAAK,qBAAqB,QAAQ,QAAQ,KAAK,IAAI,CAAC,GAAG;AACzF,QAAI,QAAQ,eAAe,OAAQ,OAAM,KAAK,yCAAyC,QAAQ,eAAe,KAAK,IAAI,CAAC,GAAG;AAC3H,QAAI,KAAK,MAAM,kBAAkB,MAAM,KAAK,GAAG,CAAC,CAAC;AAAA,EACnD;AAEA,QAAM,UAAU,MAAM,eAAe,MAAM;AAC3C,MAAI,WAAW,0BAA0B,OAAO,GAAG;AACjD,QAAI;AAAA,MACF;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,WAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,MAAM,gBAAgB,MAAM,aAAa,OAAO,CAAC;AAAA,EAC5D;AAEA,MAAI,MAAM,eAAe,QAAQ;AAC/B,UAAM,OAAO,MAAM,eAAe,KAAK,IAAI;AAC3C,QAAI,KAAK,MAAM,iBAAiB,sCAAsC,MAAM,eAAe,WAAW,IAAI,KAAK,GAAG,KAAK,IAAI,GAAG,CAAC;AAAA,EACjI;AAEA,QAAM,aAAa,OAAO,cAAc,CAAC;AACzC,MAAI,WAAW,QAAQ;AACrB,UAAM,QAAQ,WAAW,CAAC;AAC1B,UAAM,QAAQ,MAAM,SAAS,GAAG,MAAM,MAAM,WAAW,MAAM,KAAK,KAAK,OAAO,MAAM,KAAK;AACzF,UAAM,OAAO,WAAW,SAAS,IAAI,SAAS,WAAW,SAAS,CAAC,WAAW;AAC9E,QAAI,KAAK,MAAM,eAAe,GAAG,KAAK,KAAK,MAAM,OAAO,GAAG,IAAI,mDAAmD,CAAC;AAAA,EACrH;AAEA,QAAM,UAAU,4BAA4B,MAAM,aAAa,OAAO;AACtE,MAAI,OAAO,eAAe;AACxB,QAAI,WAAW,CAAC,QAAQ,UAAU;AAChC,UAAI;AAAA,QACF;AAAA,UACE;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,WAAW,yBAAyB,MAAM,WAAW,GAAG;AACtD,UAAM,OAAO,QAAQ,sBAAsB,KAAK;AAChD,QAAI;AAAA,MACF;AAAA,QACE;AAAA,QACA,yIACG,OAAO,IAAI,IAAI,KAAK;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,OAAO,kBAAkB,CAAC;AAC7C,SAAO,IAAI,OAAO,CAAC,MAAM,CAAC,WAAW,SAAS,EAAE,IAAI,CAAC;AACvD;AAGO,SAAS,kBAAkB,SAA4D;AAC5F,QAAM,EAAE,YAAY,OAAO,WAAW,IAAI,qBAAqB;AAC/D,MAAI,CAAC,WAAY,QAAO,CAAC;AACzB,QAAM,EAAE,SAAS,eAAe,eAAe,IAAI,WAAW,QAAQ;AACtE,SAAO,kBAAkB,OAAO,EAAE,SAAS,eAAe,gBAAgB,WAAW,GAAG,OAAO;AACjG;;;AC7IkC,0BAAAG,MAG9B,QAAAC,aAH8B;AAZlC,IAAM,gBAAuC;AAAA,EAC3C,sBACE;AACJ;AAOO,SAAS,aAAa,EAAE,SAAS,GAA4B;AAClE,QAAM,SAAS,kBAAkB,aAAa;AAC9C,MAAI,OAAO,WAAW,EAAG,QAAO,gBAAAD,KAAA,YAAG,UAAS;AAE5C,SACE,gBAAAC,MAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,UAAU,GACrE;AAAA,oBAAAD,KAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,WAAW,SAAS,oBAAoB,GAClG,iBAAO,IAAI,CAACE,WACX,gBAAAF,KAAC,eAA2B,OAAOE,UAAjBA,OAAM,EAAkB,CAC3C,GACH;AAAA,IACC;AAAA,KACH;AAEJ;AAEA,SAAS,YAAY,EAAE,OAAAA,OAAM,GAAuC;AAClE,QAAM,WAAWA,OAAM,SAAS,wBAAwBA,OAAM,SAAS;AACvE,SACE,gBAAAD,MAAC,SAAI,MAAMC,OAAM,UAAU,OAAO,EAAE,GAAG,YAAY,GAAGC,UAASD,OAAM,QAAQ,EAAE,GAC7E;AAAA,oBAAAF,KAAC,UAAK,eAAW,MAAC,OAAO,EAAE,YAAY,GAAG,WAAW,WAAW,GAC7D,qBAAW,gBAAAA,KAAC,YAAS,IAAK,gBAAAA,KAAC,eAAY,GAC1C;AAAA,IACA,gBAAAC,MAAC,SAAI,OAAO,EAAE,UAAU,EAAE,GACxB;AAAA,sBAAAD,KAAC,OAAE,OAAO,EAAE,QAAQ,GAAG,YAAY,IAAI,GAAI,UAAAE,OAAM,OAAM;AAAA,MACvD,gBAAAF,KAAC,OAAE,OAAO,EAAE,QAAQ,eAAe,OAAO,OAAO,gBAAgB,GAAI,UAAAE,OAAM,SAAQ;AAAA,OACrF;AAAA,KACF;AAEJ;AAEA,IAAM,aAA4B;AAAA,EAChC,WAAW;AAAA,EACX,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,QAAQ,aAAa,OAAO,MAAM;AAAA,EAClC,cAAc,OAAO;AAAA,EACrB,OAAO,OAAO;AAAA,EACd,UAAU;AAAA,EACV,YAAY;AACd;AAEA,IAAMC,YAAuE;AAAA,EAC3E,OAAO,EAAE,aAAa,OAAO,aAAa,OAAO,OAAO,YAAY;AAAA,EACpE,SAAS,EAAE,aAAa,yBAAyB,YAAY,wBAAwB;AAAA,EACrF,MAAM,EAAE,YAAY,cAAc;AACpC;AAEA,IAAM,OAAO;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAClB;AAEA,SAAS,cAAc;AACrB,SACE,gBAAAF,MAAC,SAAK,GAAG,MACP;AAAA,oBAAAD,KAAC,UAAK,GAAE,4EAA2E;AAAA,IACnF,gBAAAA,KAAC,UAAK,GAAE,WAAU;AAAA,IAClB,gBAAAA,KAAC,UAAK,GAAE,cAAa;AAAA,KACvB;AAEJ;AAEA,SAAS,WAAW;AAClB,SACE,gBAAAC,MAAC,SAAK,GAAG,MACP;AAAA,oBAAAD,KAAC,UAAK,GAAE,2BAA0B;AAAA,IAClC,gBAAAA,KAAC,UAAK,GAAE,6BAA4B;AAAA,IACpC,gBAAAA,KAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,KACvC;AAEJ;;;ANkCM,SA8DG,YAAAI,WA9DH,OAAAC,MAsBA,QAAAC,aAtBA;AA9DC,SAAS,WAAW,OAAyB,MAAM,OAA4B;AACpF,QAAM,SAAS,cAAc,MAAM,MAAM,IAAI,MAAM,SAAS;AAC5D,QAAM,UAAU,QAAQ,WAAW,MAAM;AACzC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAA6B;AAAA,IACjC,KAAK,MAAM;AAAA,IACX;AAAA,IACA,UAAU,QAAQ,YAAY,MAAM;AAAA,IACpC;AAAA,EACF;AACA,MAAI,YAAY,MAAO,OAAM,SAAS,MAAM;AAC5C,MAAI,MAAM,aAAc,OAAM,eAAe,MAAM;AACnD,MAAI,MAAM,YAAa,OAAM,cAAc,MAAM;AACjD,SAAO;AACT;AA8BO,SAAS,iBACd,QACA,eAA2D,CAAC,GACxC;AACpB,QAAM,EAAE,OAAO,SAAS,GAAG,MAAM,IAC/B,SAAS,SAAS,SAAS,EAAE,GAAG,WAAW,QAAQ,aAAa,GAAG,GAAG,MAAM,aAAa,KAAK;AAChG,QAAM,YAAY,OAAO,SAAS,WAAW,SAAS,cAAc,IAAI,IAAI;AAC5E,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,yCAAyC,OAAO,IAAI,CAAC,8BAA8B;AAAA,EACrG;AAEA,QAAM,YAAY,WAAW,SAAS;AACtC,YAAU;AAAA,IACR,gBAAAD,KAAC,cACC,0BAAAA,KAAC,kBAAgB,GAAG,OAAO,GAC7B;AAAA,EACF;AACA,SAAO,EAAE,SAAS,MAAM,UAAU,QAAQ,EAAE;AAC9C;AAMO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAAE,gBAAe;AAAA,EACf,aAAAC,eAAc;AAChB,GAAwB;AACtB,QAAM,OAAO,UAAU;AACvB,MAAI,WAAW,GAAG;AAChB,WACE,gBAAAF,MAAC,uBAAoB,SACnB;AAAA,sBAAAD,KAAC,gBAAa,UAAoB;AAAA,MAClC,gBAAAA,KAAC,yBACC,0BAAAA,KAAC,iBACC,0BAAAA,KAAC,SAAI,OAAO,MACV,0BAAAA,KAAC,QACC,0BAAAA,KAAC,YAAS,UAAU,MAClB,0BAAAA,KAAC,OAAI,GACP,GACF,GACF,GACF,GACF;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,UAAU,gBAAgB,OAAO,SAAS,QAAQ,EAAE;AAC1D,SACE,gBAAAC,MAAC,2BACC;AAAA,oBAAAD,KAAC,gBAAa,UAAoB;AAAA,IAClC,gBAAAA,KAAC,SAAI,OAAO,MAAO,gBAAM,gBAAAA,KAACE,eAAA,EAAa,IAAK,gBAAAF,KAACG,cAAA,EAAY,SAAkB,GAAG;AAAA,KAChF;AAEJ;AAEA,IAAM,OAAO,EAAE,WAAW,QAAQ,OAAO,OAAO;AAGhD,SAAS,aAAsB;AAC7B,SAAO,OAAO,WAAW,eAAe,OAAO,SAAS,OAAO;AACjE;AAGA,SAAS,aAAa,EAAE,SAAS,GAAkD;AACjF,oBAAkB,UAAU,QAAQ,EAAE,OAAO,SAAS,OAAO,aAAa,SAAS,eAAe,GAAG,IAAI,IAAI;AAC7G,SAAO;AACT;AAEA,SAAS,YAAY,EAAE,SAAS,GAA4B;AAC1D,SAAO,gBAAAH,KAAAD,WAAA,EAAG,UAAS;AACrB;","names":["useEffect","useState","useState","useEffect","useEffect","useState","jsx","useState","useEffect","useState","jsx","jsxs","useState","jsx","jsxs","alert","SEVERITY","Fragment","jsx","jsxs","LocalDevPage","ReleasePage"]}