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.
- package/CHANGELOG.md +53 -0
- package/README.md +1 -1
- package/dist/app.cjs +1139 -4336
- package/dist/app.cjs.map +1 -1
- package/dist/app.d.cts +1 -3
- package/dist/app.d.ts +1 -3
- package/dist/app.js +9 -517
- package/dist/app.js.map +1 -1
- package/dist/chunk-4ZGVKGDF.js +204 -0
- package/dist/chunk-4ZGVKGDF.js.map +1 -0
- package/dist/{chunk-YQNMIL33.js → chunk-B5ZZNLVS.js} +528 -181
- package/dist/chunk-B5ZZNLVS.js.map +1 -0
- package/dist/chunk-O7NY46QY.js +1830 -0
- package/dist/chunk-O7NY46QY.js.map +1 -0
- package/dist/chunk-P6VTEKWO.js +618 -0
- package/dist/chunk-P6VTEKWO.js.map +1 -0
- package/dist/emulator/testing.cjs +2084 -391
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.d.cts +55 -7
- package/dist/emulator/testing.d.ts +55 -7
- package/dist/emulator/testing.js +144 -19
- package/dist/emulator/testing.js.map +1 -1
- package/dist/emulator.cjs +59 -249
- package/dist/emulator.cjs.map +1 -1
- package/dist/emulator.d.cts +3 -5
- package/dist/emulator.d.ts +3 -5
- package/dist/emulator.js +5 -11
- package/dist/index.cjs +1373 -4859
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -260
- package/dist/index.d.ts +81 -260
- package/dist/index.js +8 -161
- package/dist/{inline-C7DkvuET.d.cts → templates-CGmRIQBV.d.cts} +89 -35
- package/dist/{inline-DpcT8tV9.d.ts → templates-Cq94qOcf.d.ts} +89 -35
- package/dist/{types-D3JxUCfL.d.cts → types-DBStn0Dk.d.cts} +2 -26
- package/dist/{types-D3JxUCfL.d.ts → types-DBStn0Dk.d.ts} +2 -26
- package/dist/use-grist-BK0eke9K.d.cts +358 -0
- package/dist/use-grist-C5DMZ3wU.d.ts +358 -0
- package/package.json +8 -37
- package/dist/advanced.cjs +0 -2799
- package/dist/advanced.cjs.map +0 -1
- package/dist/advanced.d.cts +0 -822
- package/dist/advanced.d.ts +0 -822
- package/dist/advanced.js +0 -875
- package/dist/advanced.js.map +0 -1
- package/dist/chunk-22U7WAY3.js +0 -463
- package/dist/chunk-22U7WAY3.js.map +0 -1
- package/dist/chunk-64SVRWW7.js +0 -758
- package/dist/chunk-64SVRWW7.js.map +0 -1
- package/dist/chunk-ELGVOHFD.js +0 -32
- package/dist/chunk-ELGVOHFD.js.map +0 -1
- package/dist/chunk-EPEBKXVN.js +0 -470
- package/dist/chunk-EPEBKXVN.js.map +0 -1
- package/dist/chunk-GTTD5HPO.js +0 -36
- package/dist/chunk-GTTD5HPO.js.map +0 -1
- package/dist/chunk-JASMM2YC.js +0 -243
- package/dist/chunk-JASMM2YC.js.map +0 -1
- package/dist/chunk-N3X46JW2.js +0 -37
- package/dist/chunk-N3X46JW2.js.map +0 -1
- package/dist/chunk-PDGEZ24B.js +0 -2366
- package/dist/chunk-PDGEZ24B.js.map +0 -1
- package/dist/chunk-PM3B4WYV.js +0 -12
- package/dist/chunk-PM3B4WYV.js.map +0 -1
- package/dist/chunk-REV5AUXD.js +0 -25
- package/dist/chunk-REV5AUXD.js.map +0 -1
- package/dist/chunk-TZB3OZA6.js +0 -1630
- package/dist/chunk-TZB3OZA6.js.map +0 -1
- package/dist/chunk-YQNMIL33.js.map +0 -1
- package/dist/create-grist-emulator-BCqp1BF9.d.ts +0 -81
- package/dist/create-grist-emulator-DBM8OSIR.d.cts +0 -81
- package/dist/emulator/devtools.cjs +0 -1404
- package/dist/emulator/devtools.cjs.map +0 -1
- package/dist/emulator/devtools.d.cts +0 -48
- package/dist/emulator/devtools.d.ts +0 -48
- package/dist/emulator/devtools.js +0 -508
- package/dist/emulator/devtools.js.map +0 -1
- package/dist/emulator/transports.cjs +0 -1132
- package/dist/emulator/transports.cjs.map +0 -1
- package/dist/emulator/transports.d.cts +0 -38
- package/dist/emulator/transports.d.ts +0 -38
- package/dist/emulator/transports.js +0 -12
- package/dist/emulator/transports.js.map +0 -1
- package/dist/templates-8k2LKoO-.d.cts +0 -24
- package/dist/templates-DIwi_mvI.d.ts +0 -24
- package/dist/use-grist-reads-DUMvJgV7.d.ts +0 -480
- package/dist/use-grist-reads-DpjVuI0H.d.cts +0 -480
- package/dist/use-grist-types-DdOLEJXN.d.cts +0 -499
- package/dist/use-grist-types-kwxoFeub.d.ts +0 -499
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.cts +0 -43
- 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
|