grist-widget-sdk 0.3.26 → 0.4.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 (90) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +1 -1
  3. package/dist/app.cjs +1139 -4336
  4. package/dist/app.cjs.map +1 -1
  5. package/dist/app.d.cts +1 -3
  6. package/dist/app.d.ts +1 -3
  7. package/dist/app.js +9 -517
  8. package/dist/app.js.map +1 -1
  9. package/dist/chunk-4ZGVKGDF.js +204 -0
  10. package/dist/chunk-4ZGVKGDF.js.map +1 -0
  11. package/dist/{chunk-YQNMIL33.js → chunk-B5ZZNLVS.js} +528 -181
  12. package/dist/chunk-B5ZZNLVS.js.map +1 -0
  13. package/dist/chunk-O7NY46QY.js +1830 -0
  14. package/dist/chunk-O7NY46QY.js.map +1 -0
  15. package/dist/chunk-P6VTEKWO.js +618 -0
  16. package/dist/chunk-P6VTEKWO.js.map +1 -0
  17. package/dist/emulator/testing.cjs +2084 -391
  18. package/dist/emulator/testing.cjs.map +1 -1
  19. package/dist/emulator/testing.d.cts +55 -7
  20. package/dist/emulator/testing.d.ts +55 -7
  21. package/dist/emulator/testing.js +144 -19
  22. package/dist/emulator/testing.js.map +1 -1
  23. package/dist/emulator.cjs +59 -249
  24. package/dist/emulator.cjs.map +1 -1
  25. package/dist/emulator.d.cts +3 -5
  26. package/dist/emulator.d.ts +3 -5
  27. package/dist/emulator.js +5 -11
  28. package/dist/index.cjs +1373 -4859
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +81 -260
  31. package/dist/index.d.ts +81 -260
  32. package/dist/index.js +8 -161
  33. package/dist/{inline-C7DkvuET.d.cts → templates-CGmRIQBV.d.cts} +89 -35
  34. package/dist/{inline-DpcT8tV9.d.ts → templates-Cq94qOcf.d.ts} +89 -35
  35. package/dist/{types-D3JxUCfL.d.cts → types-DBStn0Dk.d.cts} +2 -26
  36. package/dist/{types-D3JxUCfL.d.ts → types-DBStn0Dk.d.ts} +2 -26
  37. package/dist/use-grist-BK0eke9K.d.cts +358 -0
  38. package/dist/use-grist-C5DMZ3wU.d.ts +358 -0
  39. package/package.json +8 -37
  40. package/dist/advanced.cjs +0 -2799
  41. package/dist/advanced.cjs.map +0 -1
  42. package/dist/advanced.d.cts +0 -822
  43. package/dist/advanced.d.ts +0 -822
  44. package/dist/advanced.js +0 -875
  45. package/dist/advanced.js.map +0 -1
  46. package/dist/chunk-22U7WAY3.js +0 -463
  47. package/dist/chunk-22U7WAY3.js.map +0 -1
  48. package/dist/chunk-64SVRWW7.js +0 -758
  49. package/dist/chunk-64SVRWW7.js.map +0 -1
  50. package/dist/chunk-ELGVOHFD.js +0 -32
  51. package/dist/chunk-ELGVOHFD.js.map +0 -1
  52. package/dist/chunk-EPEBKXVN.js +0 -470
  53. package/dist/chunk-EPEBKXVN.js.map +0 -1
  54. package/dist/chunk-GTTD5HPO.js +0 -36
  55. package/dist/chunk-GTTD5HPO.js.map +0 -1
  56. package/dist/chunk-JASMM2YC.js +0 -243
  57. package/dist/chunk-JASMM2YC.js.map +0 -1
  58. package/dist/chunk-N3X46JW2.js +0 -37
  59. package/dist/chunk-N3X46JW2.js.map +0 -1
  60. package/dist/chunk-PDGEZ24B.js +0 -2366
  61. package/dist/chunk-PDGEZ24B.js.map +0 -1
  62. package/dist/chunk-PM3B4WYV.js +0 -12
  63. package/dist/chunk-PM3B4WYV.js.map +0 -1
  64. package/dist/chunk-REV5AUXD.js +0 -25
  65. package/dist/chunk-REV5AUXD.js.map +0 -1
  66. package/dist/chunk-TZB3OZA6.js +0 -1630
  67. package/dist/chunk-TZB3OZA6.js.map +0 -1
  68. package/dist/chunk-YQNMIL33.js.map +0 -1
  69. package/dist/create-grist-emulator-BCqp1BF9.d.ts +0 -81
  70. package/dist/create-grist-emulator-DBM8OSIR.d.cts +0 -81
  71. package/dist/emulator/devtools.cjs +0 -1404
  72. package/dist/emulator/devtools.cjs.map +0 -1
  73. package/dist/emulator/devtools.d.cts +0 -48
  74. package/dist/emulator/devtools.d.ts +0 -48
  75. package/dist/emulator/devtools.js +0 -508
  76. package/dist/emulator/devtools.js.map +0 -1
  77. package/dist/emulator/transports.cjs +0 -1132
  78. package/dist/emulator/transports.cjs.map +0 -1
  79. package/dist/emulator/transports.d.cts +0 -38
  80. package/dist/emulator/transports.d.ts +0 -38
  81. package/dist/emulator/transports.js +0 -12
  82. package/dist/emulator/transports.js.map +0 -1
  83. package/dist/templates-8k2LKoO-.d.cts +0 -24
  84. package/dist/templates-DIwi_mvI.d.ts +0 -24
  85. package/dist/use-grist-reads-DUMvJgV7.d.ts +0 -480
  86. package/dist/use-grist-reads-DpjVuI0H.d.cts +0 -480
  87. package/dist/use-grist-types-DdOLEJXN.d.cts +0 -499
  88. package/dist/use-grist-types-kwxoFeub.d.ts +0 -499
  89. package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.cts +0 -43
  90. package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.ts +0 -43
@@ -0,0 +1,618 @@
1
+ import {
2
+ GristBoundary,
3
+ GristWidgetProvider,
4
+ deriveColumnMappingStatus,
5
+ deriveStatus,
6
+ isAccessInsufficientError,
7
+ parseGristWidgetLinkingInfo,
8
+ useNearestGristState
9
+ } from "./chunk-O7NY46QY.js";
10
+
11
+ // src/app/deploy-path.ts
12
+ var VERSION_SEGMENT = /^v(\d+\.\d+\.\d+(?:-[\w.]+)?)$/;
13
+ function parseDeployPath(pathname) {
14
+ const segments = pathname.replace(/\/+$/, "").split("/");
15
+ const last = segments[segments.length - 1] ?? "";
16
+ let channel = null;
17
+ if (last === "latest") channel = { kind: "latest" };
18
+ else if (last === "dev") channel = { kind: "dev" };
19
+ else {
20
+ const match = VERSION_SEGMENT.exec(last);
21
+ if (match) channel = { kind: "version", version: match[1] };
22
+ }
23
+ if (!channel) return { channel: { kind: "latest" }, basePath: withTrailingSlash(pathname) };
24
+ return { channel, basePath: withTrailingSlash(segments.slice(0, -1).join("/")) };
25
+ }
26
+ function deployChannelLabel(channel) {
27
+ return channel.kind === "version" ? `v${channel.version}` : channel.kind;
28
+ }
29
+ function withTrailingSlash(path) {
30
+ return path.endsWith("/") ? path : `${path}/`;
31
+ }
32
+
33
+ // src/app/released-versions.ts
34
+ import { useEffect, useState } from "react";
35
+ function useReleasedVersions(pathname = typeof window === "undefined" ? "/" : window.location.pathname) {
36
+ const url = `${parseDeployPath(pathname).basePath}versions.json`;
37
+ const [result, setResult] = useState({ versions: null, error: false });
38
+ useEffect(() => {
39
+ let cancelled = false;
40
+ fetch(url, { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`))).then((data) => {
41
+ if (cancelled) return;
42
+ if (!Array.isArray(data)) throw new Error("versions.json is not an array");
43
+ setResult({ versions: data, error: false });
44
+ }).catch(() => {
45
+ if (!cancelled) setResult({ versions: null, error: true });
46
+ });
47
+ return () => {
48
+ cancelled = true;
49
+ };
50
+ }, [url]);
51
+ return result;
52
+ }
53
+
54
+ // src/app/widget-theme.tsx
55
+ import { createContext, useCallback, useContext, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
56
+ import { jsx } from "react/jsx-runtime";
57
+ var STORAGE_KEY = "theme";
58
+ var DARK_QUERY = "(prefers-color-scheme: dark)";
59
+ var THEMES = ["light", "dark", "system"];
60
+ var ThemeContext = createContext(null);
61
+ function useTheme() {
62
+ const value = useContext(ThemeContext);
63
+ return value ?? { theme: "system", resolvedTheme: systemTheme(), setTheme: noop };
64
+ }
65
+ function EmbeddedThemeProvider({ children }) {
66
+ const gristTheme = useNearestGristState().state.theme;
67
+ const system = useSystemTheme(gristTheme === null);
68
+ const resolvedTheme = gristTheme ?? system;
69
+ useApplyTheme(resolvedTheme);
70
+ const value = useMemo(
71
+ () => ({ theme: gristTheme ?? "system", resolvedTheme, setTheme: noop }),
72
+ [gristTheme, resolvedTheme]
73
+ );
74
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
75
+ }
76
+ function StandaloneThemeProvider({ children }) {
77
+ const [theme, setThemeState] = useState2(() => readStoredTheme() ?? "system");
78
+ const system = useSystemTheme(theme === "system");
79
+ const resolvedTheme = theme === "system" ? system : theme;
80
+ useApplyTheme(resolvedTheme);
81
+ const setTheme = useCallback((next) => {
82
+ writeStoredTheme(next);
83
+ setThemeState(next);
84
+ }, []);
85
+ useEffect2(() => {
86
+ const onKeyDown = (event) => {
87
+ if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) return;
88
+ if (event.key.toLowerCase() !== "d" || isEditableTarget(event.target)) return;
89
+ setThemeState((current) => {
90
+ const currentResolved = current === "system" ? systemTheme() : current;
91
+ const next = currentResolved === "dark" ? "light" : "dark";
92
+ writeStoredTheme(next);
93
+ return next;
94
+ });
95
+ };
96
+ window.addEventListener("keydown", onKeyDown);
97
+ return () => window.removeEventListener("keydown", onKeyDown);
98
+ }, []);
99
+ useEffect2(() => {
100
+ const onStorage = (event) => {
101
+ if (event.key !== STORAGE_KEY) return;
102
+ setThemeState(isTheme(event.newValue) ? event.newValue : "system");
103
+ };
104
+ window.addEventListener("storage", onStorage);
105
+ return () => window.removeEventListener("storage", onStorage);
106
+ }, []);
107
+ const value = useMemo(
108
+ () => ({ theme, resolvedTheme, setTheme }),
109
+ [theme, resolvedTheme, setTheme]
110
+ );
111
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
112
+ }
113
+ function useSystemTheme(active) {
114
+ const [theme, setTheme] = useState2(systemTheme);
115
+ useEffect2(() => {
116
+ if (!active || typeof window.matchMedia !== "function") return;
117
+ const query = window.matchMedia(DARK_QUERY);
118
+ const onChange = () => setTheme(query.matches ? "dark" : "light");
119
+ onChange();
120
+ query.addEventListener("change", onChange);
121
+ return () => query.removeEventListener("change", onChange);
122
+ }, [active]);
123
+ return theme;
124
+ }
125
+ function useApplyTheme(theme) {
126
+ useEffect2(() => {
127
+ const root = document.documentElement;
128
+ if (root.classList.contains(theme) && !root.classList.contains(other(theme))) return;
129
+ const restoreTransitions = suspendTransitions();
130
+ root.classList.remove(other(theme));
131
+ root.classList.add(theme);
132
+ restoreTransitions();
133
+ }, [theme]);
134
+ }
135
+ function systemTheme() {
136
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
137
+ return window.matchMedia(DARK_QUERY).matches ? "dark" : "light";
138
+ }
139
+ function other(theme) {
140
+ return theme === "dark" ? "light" : "dark";
141
+ }
142
+ function suspendTransitions() {
143
+ const style = document.createElement("style");
144
+ style.appendChild(
145
+ document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;transition:none!important}")
146
+ );
147
+ document.head.appendChild(style);
148
+ return () => {
149
+ window.getComputedStyle(document.body);
150
+ requestAnimationFrame(() => requestAnimationFrame(() => style.remove()));
151
+ };
152
+ }
153
+ function isEditableTarget(target) {
154
+ if (!(target instanceof HTMLElement)) return false;
155
+ return target.isContentEditable || target.closest("input, textarea, select, [contenteditable='true']") !== null;
156
+ }
157
+ function isTheme(value) {
158
+ return value !== null && THEMES.includes(value);
159
+ }
160
+ function readStoredTheme() {
161
+ try {
162
+ const value = localStorage.getItem(STORAGE_KEY);
163
+ return isTheme(value) ? value : null;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ function writeStoredTheme(theme) {
169
+ try {
170
+ localStorage.setItem(STORAGE_KEY, theme);
171
+ } catch {
172
+ }
173
+ }
174
+ function noop() {
175
+ }
176
+
177
+ // src/app/mount-grist-widget.tsx
178
+ import { StrictMode, Suspense } from "react";
179
+ import { createRoot } from "react-dom/client";
180
+
181
+ // src/app/chrome/tokens.ts
182
+ var token = (name, fallback) => `var(--${name}, ${fallback})`;
183
+ var tokens = {
184
+ background: token("background", "#ffffff"),
185
+ foreground: token("foreground", "#0a0a0a"),
186
+ card: token("card", "#ffffff"),
187
+ mutedForeground: token("muted-foreground", "#737373"),
188
+ border: token("border", "#e5e5e5"),
189
+ primary: token("primary", "#171717"),
190
+ primaryForeground: token("primary-foreground", "#fafafa"),
191
+ destructive: token("destructive", "#dc2626"),
192
+ radius: token("radius", "0.625rem")
193
+ };
194
+ var pageStyle = {
195
+ boxSizing: "border-box",
196
+ display: "flex",
197
+ flexDirection: "column",
198
+ justifyContent: "center",
199
+ maxWidth: "28rem",
200
+ minHeight: "100%",
201
+ margin: "0 auto",
202
+ padding: "4rem 1.5rem",
203
+ color: tokens.foreground,
204
+ font: "inherit"
205
+ };
206
+ var eyebrowStyle = {
207
+ margin: "0 0 0.5rem",
208
+ textAlign: "center",
209
+ fontSize: "0.875rem",
210
+ fontWeight: 500,
211
+ color: tokens.mutedForeground
212
+ };
213
+ var headingStyle = {
214
+ margin: 0,
215
+ textAlign: "center",
216
+ fontSize: "1.5rem",
217
+ fontWeight: 600,
218
+ letterSpacing: "-0.01em"
219
+ };
220
+ var leadStyle = {
221
+ margin: "0.75rem auto 2rem",
222
+ maxWidth: "24rem",
223
+ textAlign: "center",
224
+ fontSize: "0.875rem",
225
+ lineHeight: 1.5,
226
+ color: tokens.mutedForeground
227
+ };
228
+ var cardStyle = {
229
+ boxSizing: "border-box",
230
+ marginBottom: "1rem",
231
+ padding: "1.25rem",
232
+ border: `1px solid ${tokens.border}`,
233
+ borderRadius: tokens.radius,
234
+ background: tokens.card
235
+ };
236
+ var cardTitleStyle = {
237
+ margin: "0 0 0.375rem",
238
+ fontSize: "1rem",
239
+ fontWeight: 600
240
+ };
241
+ var cardTextStyle = {
242
+ margin: 0,
243
+ fontSize: "0.875rem",
244
+ lineHeight: 1.5,
245
+ color: tokens.mutedForeground
246
+ };
247
+ var codeStyle = {
248
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
249
+ fontSize: "0.8125rem",
250
+ color: tokens.foreground
251
+ };
252
+
253
+ // src/app/chrome/widget-title.ts
254
+ function widgetTitle() {
255
+ const title = typeof document === "undefined" ? "" : document.title.trim();
256
+ return title || "Grist widget";
257
+ }
258
+
259
+ // src/app/chrome/local-dev-page.tsx
260
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
261
+ function LocalDevPage() {
262
+ return /* @__PURE__ */ jsxs("main", { style: pageStyle, children: [
263
+ /* @__PURE__ */ jsxs("p", { style: eyebrowStyle, children: [
264
+ widgetTitle(),
265
+ " \xB7 local dev"
266
+ ] }),
267
+ /* @__PURE__ */ jsx2("h1", { style: headingStyle, children: "Not embedded in Grist" }),
268
+ /* @__PURE__ */ jsx2("p", { style: leadStyle, children: "This is the local dev server: there's no Grist here to render the widget against." }),
269
+ /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
270
+ /* @__PURE__ */ jsx2("h2", { style: cardTitleStyle, children: "Check it against real Grist" }),
271
+ /* @__PURE__ */ jsxs("p", { style: cardTextStyle, children: [
272
+ "Push to ",
273
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "dev" }),
274
+ ", then open its ",
275
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "/dev/" }),
276
+ " URL in a custom widget of a real Grist document: later pushes reload it there within seconds. Locally,",
277
+ " ",
278
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "pnpm test" }),
279
+ " runs the widget against an emulated Grist."
280
+ ] })
281
+ ] }),
282
+ /* @__PURE__ */ jsxs("section", { style: cardStyle, children: [
283
+ /* @__PURE__ */ jsx2("h2", { style: cardTitleStyle, children: "Starting from another example?" }),
284
+ /* @__PURE__ */ jsxs("p", { style: cardTextStyle, children: [
285
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "npx create-grist-widget list" }),
286
+ " shows the examples, and",
287
+ " ",
288
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: "npx create-grist-widget use <example>" }),
289
+ " swaps this widget for one."
290
+ ] })
291
+ ] })
292
+ ] });
293
+ }
294
+
295
+ // src/app/chrome/release-page.tsx
296
+ import { useState as useState3 } from "react";
297
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
298
+ function ReleasePage({ channel }) {
299
+ const label = deployChannelLabel(channel);
300
+ return /* @__PURE__ */ jsxs2("main", { style: pageStyle, children: [
301
+ /* @__PURE__ */ jsxs2("p", { style: eyebrowStyle, children: [
302
+ widgetTitle(),
303
+ " \xB7 ",
304
+ label
305
+ ] }),
306
+ /* @__PURE__ */ jsx3("h1", { style: headingStyle, children: "Grist isn't loaded here" }),
307
+ /* @__PURE__ */ jsxs2("p", { style: leadStyle, children: [
308
+ "This is the ",
309
+ /* @__PURE__ */ jsx3("code", { style: codeStyle, children: label }),
310
+ " build, opened outside Grist. Hover a version to see when it was released."
311
+ ] }),
312
+ /* @__PURE__ */ jsx3(ChannelLinks, { current: channel }),
313
+ /* @__PURE__ */ jsx3(InstallUrl, {})
314
+ ] });
315
+ }
316
+ function ChannelLinks({ current }) {
317
+ const { basePath } = parseDeployPath(window.location.pathname);
318
+ const { versions } = useReleasedVersions();
319
+ const isCurrent = (channel) => channel.kind === current.kind && (channel.kind !== "version" || current.kind === "version" && channel.version === current.version);
320
+ return /* @__PURE__ */ jsxs2("nav", { "aria-label": "Versions", style: { display: "flex", flexWrap: "wrap", justifyContent: "center", gap: "0.5rem", marginBottom: "2rem" }, children: [
321
+ /* @__PURE__ */ jsx3(ChannelLink, { href: `${basePath}latest/`, active: isCurrent({ kind: "latest" }), children: "latest" }),
322
+ /* @__PURE__ */ jsx3(ChannelLink, { href: `${basePath}dev/`, active: isCurrent({ kind: "dev" }), children: "dev" }),
323
+ versions?.map((v) => /* @__PURE__ */ jsx3(
324
+ ChannelLink,
325
+ {
326
+ href: `${basePath}v${v.version}/`,
327
+ title: formatDate(v.publishedAt),
328
+ active: isCurrent({ kind: "version", version: v.version }),
329
+ children: `v${v.version}`
330
+ },
331
+ v.version
332
+ ))
333
+ ] });
334
+ }
335
+ function ChannelLink({
336
+ href,
337
+ title,
338
+ active,
339
+ children
340
+ }) {
341
+ const style = {
342
+ padding: "0.25rem 0.75rem",
343
+ border: `1px solid ${active ? tokens.primary : tokens.border}`,
344
+ borderRadius: "9999px",
345
+ background: active ? tokens.primary : "transparent",
346
+ color: active ? tokens.primaryForeground : tokens.mutedForeground,
347
+ fontSize: "0.75rem",
348
+ fontWeight: 500,
349
+ textDecoration: "none"
350
+ };
351
+ return /* @__PURE__ */ jsx3("a", { href, title, "aria-current": active ? "page" : void 0, style, children });
352
+ }
353
+ function InstallUrl() {
354
+ const url = window.location.href;
355
+ const [copied, setCopied] = useState3(false);
356
+ function copy() {
357
+ void navigator.clipboard?.writeText(url).then(() => {
358
+ setCopied(true);
359
+ setTimeout(() => setCopied(false), 1500);
360
+ });
361
+ }
362
+ return /* @__PURE__ */ jsxs2("section", { style: cardStyle, children: [
363
+ /* @__PURE__ */ jsx3("h2", { style: cardTitleStyle, children: "Use it as a Grist custom widget" }),
364
+ /* @__PURE__ */ jsx3("p", { style: { ...cardTextStyle, marginBottom: "1rem" }, children: "Paste this URL into a custom widget's URL field in Grist." }),
365
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "0.5rem" }, children: [
366
+ /* @__PURE__ */ jsx3(
367
+ "input",
368
+ {
369
+ readOnly: true,
370
+ value: url,
371
+ "aria-label": "Widget URL",
372
+ onFocus: (event) => event.target.select(),
373
+ style: {
374
+ flex: 1,
375
+ minWidth: 0,
376
+ padding: "0.5rem 0.75rem",
377
+ border: `1px solid ${tokens.border}`,
378
+ borderRadius: tokens.radius,
379
+ background: "transparent",
380
+ color: tokens.foreground,
381
+ font: "inherit",
382
+ fontSize: "0.875rem"
383
+ }
384
+ }
385
+ ),
386
+ /* @__PURE__ */ jsx3(
387
+ "button",
388
+ {
389
+ type: "button",
390
+ onClick: copy,
391
+ style: {
392
+ padding: "0.5rem 1rem",
393
+ border: "none",
394
+ borderRadius: tokens.radius,
395
+ background: tokens.primary,
396
+ color: tokens.primaryForeground,
397
+ font: "inherit",
398
+ fontSize: "0.875rem",
399
+ fontWeight: 500,
400
+ cursor: "pointer"
401
+ },
402
+ children: copied ? "Copied" : "Copy"
403
+ }
404
+ )
405
+ ] })
406
+ ] });
407
+ }
408
+ function formatDate(iso) {
409
+ const date = new Date(iso);
410
+ return Number.isNaN(date.getTime()) ? iso : date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
411
+ }
412
+
413
+ // src/sdk/helpers/grist-sdk-alerts.ts
414
+ var TITLE = {
415
+ "column-mapping": "Column mapping",
416
+ "mapping-pending": "Column mapping",
417
+ "section-not-linked": "Widget linking",
418
+ "source-not-wired": "Widget linking",
419
+ "action-error": "Action error",
420
+ "access-insufficient": "Access level",
421
+ "map-back-skip": "Write skipped",
422
+ "cell-issues": "Unexpected values"
423
+ };
424
+ var SEVERITY = {
425
+ "column-mapping": "warning",
426
+ "mapping-pending": "info",
427
+ "section-not-linked": "warning",
428
+ "source-not-wired": "warning",
429
+ "action-error": "error",
430
+ "access-insufficient": "error",
431
+ "map-back-skip": "warning",
432
+ "cell-issues": "warning"
433
+ };
434
+ var alert = (kind, message, ariaRole = "status") => ({
435
+ id: kind,
436
+ kind,
437
+ severity: SEVERITY[kind],
438
+ title: TITLE[kind],
439
+ ariaRole,
440
+ message
441
+ });
442
+ function isWidgetSectionNotLinked(interaction) {
443
+ const linking = parseGristWidgetLinkingInfo(interaction?.linking);
444
+ return Boolean(linking) && linking.asTarget == null;
445
+ }
446
+ function getGristSdkAlerts(state, widget, options = {}) {
447
+ if (deriveStatus(state) !== "ready") return [];
448
+ const out = [];
449
+ const mapping = deriveColumnMappingStatus(widget.columns, state.mappings);
450
+ if (mapping.pending) out.push(alert("mapping-pending", "Waiting for Grist to report column mappings."));
451
+ else if (!mapping.ok) {
452
+ const parts = ["Column mapping is incomplete."];
453
+ if (mapping.missing.length) parts.push(`Required columns: ${mapping.missing.join(", ")}.`);
454
+ if (mapping.emptyMultiples.length) parts.push(`Assign at least one sheet column for: ${mapping.emptyMultiples.join(", ")}.`);
455
+ out.push(alert("column-mapping", parts.join(" ")));
456
+ }
457
+ const failure = state.actionError ?? state.readError;
458
+ if (failure && isAccessInsufficientError(failure)) {
459
+ out.push(
460
+ alert(
461
+ "access-insufficient",
462
+ 'This operation requires "Full document access". Set the Access level to "Full document access" in the Custom widget configuration panel.',
463
+ "alert"
464
+ )
465
+ );
466
+ } else if (state.actionError) {
467
+ out.push(alert("action-error", state.actionError, "alert"));
468
+ }
469
+ if (state.mapBackSkipped.length) {
470
+ const list = state.mapBackSkipped.join(", ");
471
+ out.push(alert("map-back-skip", `mapBack skipped multi-mapped column${state.mapBackSkipped.length === 1 ? "" : "s"}: ${list}.`));
472
+ }
473
+ const cellIssues = widget.cellIssues ?? [];
474
+ if (cellIssues.length) {
475
+ const first = cellIssues[0];
476
+ const where = first.column ? `${first.column} in row ${first.rowId}` : `row ${first.rowId}`;
477
+ const more = cellIssues.length > 1 ? ` (and ${cellIssues.length - 1} more)` : "";
478
+ out.push(alert("cell-issues", `${where}: ${first.message}${more}. Check the column's type, or the cell, in Grist.`));
479
+ }
480
+ const linking = parseGristWidgetLinkingInfo(state.interaction?.linking);
481
+ if (widget.allowSelectBy) {
482
+ if (linking && !linking.asSource) {
483
+ out.push(
484
+ alert(
485
+ "source-not-wired",
486
+ "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."
487
+ )
488
+ );
489
+ }
490
+ } else if (isWidgetSectionNotLinked(state.interaction)) {
491
+ const hint = options.sectionNotLinkedHint?.trim();
492
+ out.push(
493
+ alert(
494
+ "section-not-linked",
495
+ "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." + (hint ? ` ${hint}` : "")
496
+ )
497
+ );
498
+ }
499
+ const suppressed = widget.suppressAlerts ?? [];
500
+ return out.filter((a) => !suppressed.includes(a.kind));
501
+ }
502
+ function useGristSdkAlerts(options) {
503
+ const { connection, state, cellIssues } = useNearestGristState();
504
+ if (!connection) return [];
505
+ const { columns, allowSelectBy, suppressAlerts } = connection.runtime.options;
506
+ return getGristSdkAlerts(state, { columns, allowSelectBy, suppressAlerts, cellIssues }, options);
507
+ }
508
+
509
+ // src/app/chrome/widget-alerts.tsx
510
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
511
+ var ALERT_OPTIONS = {
512
+ sectionNotLinkedHint: "In Grist, select this widget section on the page, then use Link to connect a table, card, or chart view as the selector."
513
+ };
514
+ function WidgetAlerts({ children }) {
515
+ const alerts = useGristSdkAlerts(ALERT_OPTIONS);
516
+ if (alerts.length === 0) return /* @__PURE__ */ jsx4(Fragment, { children });
517
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
518
+ /* @__PURE__ */ jsx4("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", padding: "0.75rem 0.75rem 0" }, children: alerts.map((alert2) => /* @__PURE__ */ jsx4(WidgetAlert, { alert: alert2 }, alert2.id)) }),
519
+ children
520
+ ] });
521
+ }
522
+ function WidgetAlert({ alert: alert2 }) {
523
+ const linkIcon = alert2.kind === "section-not-linked" || alert2.kind === "source-not-wired";
524
+ return /* @__PURE__ */ jsxs3("div", { role: alert2.ariaRole, style: { ...alertStyle, ...SEVERITY2[alert2.severity] }, children: [
525
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": true, style: { flexShrink: 0, marginTop: "0.125rem" }, children: linkIcon ? /* @__PURE__ */ jsx4(LinkIcon, {}) : /* @__PURE__ */ jsx4(WarningIcon, {}) }),
526
+ /* @__PURE__ */ jsxs3("div", { style: { minWidth: 0 }, children: [
527
+ /* @__PURE__ */ jsx4("p", { style: { margin: 0, fontWeight: 500 }, children: alert2.title }),
528
+ /* @__PURE__ */ jsx4("p", { style: { margin: "0.25rem 0 0", color: tokens.mutedForeground }, children: alert2.message })
529
+ ] })
530
+ ] });
531
+ }
532
+ var alertStyle = {
533
+ boxSizing: "border-box",
534
+ display: "flex",
535
+ gap: "0.75rem",
536
+ padding: "0.75rem 1rem",
537
+ border: `1px solid ${tokens.border}`,
538
+ borderRadius: tokens.radius,
539
+ color: tokens.foreground,
540
+ fontSize: "0.875rem",
541
+ lineHeight: 1.5
542
+ };
543
+ var SEVERITY2 = {
544
+ error: { borderColor: tokens.destructive, color: tokens.destructive },
545
+ warning: { borderColor: "rgb(245 158 11 / 0.4)", background: "rgb(245 158 11 / 0.1)" },
546
+ info: { background: "transparent" }
547
+ };
548
+ var ICON = {
549
+ width: 16,
550
+ height: 16,
551
+ viewBox: "0 0 24 24",
552
+ fill: "none",
553
+ stroke: "currentColor",
554
+ strokeWidth: 2,
555
+ strokeLinecap: "round",
556
+ strokeLinejoin: "round"
557
+ };
558
+ function WarningIcon() {
559
+ return /* @__PURE__ */ jsxs3("svg", { ...ICON, children: [
560
+ /* @__PURE__ */ jsx4("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" }),
561
+ /* @__PURE__ */ jsx4("path", { d: "M12 9v4" }),
562
+ /* @__PURE__ */ jsx4("path", { d: "M12 17h.01" })
563
+ ] });
564
+ }
565
+ function LinkIcon() {
566
+ return /* @__PURE__ */ jsxs3("svg", { ...ICON, children: [
567
+ /* @__PURE__ */ jsx4("path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }),
568
+ /* @__PURE__ */ jsx4("path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }),
569
+ /* @__PURE__ */ jsx4("line", { x1: "8", x2: "16", y1: "12", y2: "12" })
570
+ ] });
571
+ }
572
+
573
+ // 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;
577
+ const container = typeof root === "string" ? document.querySelector(root) : root;
578
+ if (!container) {
579
+ throw new Error(`mountGristWidget: no element matches "${String(root)}" to render the widget into.`);
580
+ }
581
+ const reactRoot = createRoot(container);
582
+ reactRoot.render(
583
+ /* @__PURE__ */ jsx5(StrictMode, { children: /* @__PURE__ */ jsx5(GristWidgetApp, { ...props }) })
584
+ );
585
+ return { unmount: () => reactRoot.unmount() };
586
+ }
587
+ function GristWidgetApp({
588
+ App,
589
+ options,
590
+ dev = false,
591
+ Alerts = WidgetAlerts,
592
+ LocalDevPage: LocalDevPage2 = LocalDevPage,
593
+ ReleasePage: ReleasePage2 = ReleasePage
594
+ }) {
595
+ const Wrap = Alerts ?? PassThrough;
596
+ 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, {}) }) }) }) }) }) });
598
+ }
599
+ 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 }) }) });
601
+ }
602
+ var FILL = { minHeight: "100%", width: "100%" };
603
+ function isEmbedded() {
604
+ return typeof window !== "undefined" && window.self !== window.top;
605
+ }
606
+ function PassThrough({ children }) {
607
+ return /* @__PURE__ */ jsx5(Fragment2, { children });
608
+ }
609
+
610
+ export {
611
+ parseDeployPath,
612
+ deployChannelLabel,
613
+ useReleasedVersions,
614
+ useTheme,
615
+ mountGristWidget,
616
+ GristWidgetApp
617
+ };
618
+ //# sourceMappingURL=chunk-P6VTEKWO.js.map