grist-widget-sdk 0.3.26 → 0.3.27
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 +8 -0
- package/dist/advanced.js +10 -13
- package/dist/advanced.js.map +1 -1
- package/dist/app.js +10 -514
- package/dist/app.js.map +1 -1
- package/dist/chunk-6Y2IONGJ.js +519 -0
- package/dist/chunk-6Y2IONGJ.js.map +1 -0
- package/dist/{chunk-PDGEZ24B.js → chunk-B4AKVSWV.js} +16 -20
- package/dist/{chunk-PDGEZ24B.js.map → chunk-B4AKVSWV.js.map} +1 -1
- package/dist/{chunk-TZB3OZA6.js → chunk-NO6FYX33.js} +782 -328
- package/dist/chunk-NO6FYX33.js.map +1 -0
- package/dist/{chunk-JASMM2YC.js → chunk-OPTOYD2Y.js} +36 -2
- package/dist/chunk-OPTOYD2Y.js.map +1 -0
- package/dist/emulator/devtools.js +2 -4
- package/dist/emulator/devtools.js.map +1 -1
- package/dist/emulator/testing.cjs +5098 -87
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.d.cts +55 -4
- package/dist/emulator/testing.d.ts +55 -4
- package/dist/emulator/testing.js +142 -4
- package/dist/emulator/testing.js.map +1 -1
- package/dist/index.js +4 -7
- package/package.json +1 -1
- package/dist/chunk-EPEBKXVN.js +0 -470
- package/dist/chunk-EPEBKXVN.js.map +0 -1
- 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-TZB3OZA6.js.map +0 -1
package/dist/chunk-EPEBKXVN.js
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
acquireEmbeddedHandshakeManager,
|
|
3
|
-
deriveCapabilities,
|
|
4
|
-
deriveErrorMessage,
|
|
5
|
-
deriveStatus,
|
|
6
|
-
ensureGristReady,
|
|
7
|
-
initialGristSnapshot,
|
|
8
|
-
releaseEmbeddedHandshakeManager,
|
|
9
|
-
resetGristReadySingleton
|
|
10
|
-
} from "./chunk-TZB3OZA6.js";
|
|
11
|
-
|
|
12
|
-
// src/sdk/helpers/grist-fetch-with-auth.ts
|
|
13
|
-
var TOKEN_TTL_MS = 45e3;
|
|
14
|
-
var cache = /* @__PURE__ */ new Map();
|
|
15
|
-
var inFlight = /* @__PURE__ */ new Map();
|
|
16
|
-
function cacheKey(readOnly) {
|
|
17
|
-
return readOnly ? "ro" : "rw";
|
|
18
|
-
}
|
|
19
|
-
async function getGristAccessToken(docApi, options) {
|
|
20
|
-
if (!docApi.getAccessToken) {
|
|
21
|
-
throw new Error("getAccessToken is not available on docApi (need higher access level)");
|
|
22
|
-
}
|
|
23
|
-
const readOnly = options?.readOnly ?? false;
|
|
24
|
-
const key = cacheKey(readOnly);
|
|
25
|
-
const now = Date.now();
|
|
26
|
-
const hit = cache.get(key);
|
|
27
|
-
if (hit && now - hit.fetchedAt < TOKEN_TTL_MS) {
|
|
28
|
-
return hit.token;
|
|
29
|
-
}
|
|
30
|
-
const pending = inFlight.get(key);
|
|
31
|
-
if (pending) return pending;
|
|
32
|
-
const next = docApi.getAccessToken({ readOnly }).then((token) => {
|
|
33
|
-
cache.set(key, { token, fetchedAt: Date.now() });
|
|
34
|
-
inFlight.delete(key);
|
|
35
|
-
return token;
|
|
36
|
-
});
|
|
37
|
-
inFlight.set(key, next);
|
|
38
|
-
return next;
|
|
39
|
-
}
|
|
40
|
-
function clearGristAccessTokenCache() {
|
|
41
|
-
cache.clear();
|
|
42
|
-
inFlight.clear();
|
|
43
|
-
}
|
|
44
|
-
async function fetchWithGristAuth(docApi, path, init) {
|
|
45
|
-
const readOnly = init?.readOnly ?? false;
|
|
46
|
-
let token;
|
|
47
|
-
try {
|
|
48
|
-
token = await getGristAccessToken(docApi, { readOnly });
|
|
49
|
-
} catch (err) {
|
|
50
|
-
clearGristAccessTokenCache();
|
|
51
|
-
throw err;
|
|
52
|
-
}
|
|
53
|
-
const isAbsolute = /^https?:\/\//i.test(path);
|
|
54
|
-
const cleanPath = isAbsolute ? path : path.startsWith("/") ? path : `/${path}`;
|
|
55
|
-
const url = isAbsolute ? new URL(path) : new URL(`${token.baseUrl.replace(/\/$/, "")}${cleanPath}`);
|
|
56
|
-
if (!url.searchParams.has("auth")) url.searchParams.set("auth", token.token);
|
|
57
|
-
const headers = new Headers(init?.headers ?? {});
|
|
58
|
-
headers.delete("Authorization");
|
|
59
|
-
const response = await fetch(url.toString(), { ...init, headers });
|
|
60
|
-
if (response.status === 401) {
|
|
61
|
-
clearGristAccessTokenCache();
|
|
62
|
-
const retryToken = await getGristAccessToken(docApi, { readOnly });
|
|
63
|
-
url.searchParams.set("auth", retryToken.token);
|
|
64
|
-
return fetch(url.toString(), { ...init, headers });
|
|
65
|
-
}
|
|
66
|
-
return response;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/sdk/internal/hooks/use-grist-core.ts
|
|
70
|
-
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
71
|
-
|
|
72
|
-
// src/sdk/internal/handshake-manager-context.ts
|
|
73
|
-
import { createContext, useContext, useEffect, useRef } from "react";
|
|
74
|
-
|
|
75
|
-
// src/sdk/internal/build-manager-options.ts
|
|
76
|
-
function buildManagerOptionsFromGristOptions(options) {
|
|
77
|
-
const opts = {
|
|
78
|
-
requiredAccess: options?.requiredAccess ?? "read table",
|
|
79
|
-
columns: options?.columns ?? [],
|
|
80
|
-
allowSelectBy: options?.allowSelectBy,
|
|
81
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
82
|
-
onEditOptions: options?.onEditOptions,
|
|
83
|
-
negotiate: {
|
|
84
|
-
readyImpl: (payload) => ensureGristReady(payload)
|
|
85
|
-
},
|
|
86
|
-
onBeforeReload: () => resetGristReadySingleton()
|
|
87
|
-
};
|
|
88
|
-
if (options?.availabilityMaxAttempts !== void 0 || options?.availabilityIntervalMs !== void 0) {
|
|
89
|
-
const interval = options?.availabilityIntervalMs ?? 1e3;
|
|
90
|
-
const max = options?.availabilityMaxAttempts ?? 10;
|
|
91
|
-
opts.detect = {
|
|
92
|
-
pollSequenceMs: Array.from({ length: max }, () => interval),
|
|
93
|
-
budgetMs: interval * max
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return opts;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/sdk/internal/handshake-manager-context.ts
|
|
100
|
-
var GristHandshakeManagerContext = createContext(null);
|
|
101
|
-
function useGristManagerOptional() {
|
|
102
|
-
return useContext(GristHandshakeManagerContext);
|
|
103
|
-
}
|
|
104
|
-
function useGristManager(options) {
|
|
105
|
-
const fromProvider = useContext(GristHandshakeManagerContext);
|
|
106
|
-
const optionsRef = useRef(options);
|
|
107
|
-
optionsRef.current = options;
|
|
108
|
-
const embeddedRef = useRef(null);
|
|
109
|
-
const ownsEmbeddedRef = useRef(false);
|
|
110
|
-
if (fromProvider) {
|
|
111
|
-
return fromProvider;
|
|
112
|
-
}
|
|
113
|
-
if (embeddedRef.current === null) {
|
|
114
|
-
embeddedRef.current = acquireEmbeddedHandshakeManager(
|
|
115
|
-
buildManagerOptionsFromGristOptions(optionsRef.current)
|
|
116
|
-
);
|
|
117
|
-
ownsEmbeddedRef.current = true;
|
|
118
|
-
}
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (!ownsEmbeddedRef.current) return;
|
|
121
|
-
return () => {
|
|
122
|
-
releaseEmbeddedHandshakeManager();
|
|
123
|
-
embeddedRef.current = null;
|
|
124
|
-
ownsEmbeddedRef.current = false;
|
|
125
|
-
};
|
|
126
|
-
}, []);
|
|
127
|
-
return embeddedRef.current;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/sdk/internal/hooks/use-grist-core.ts
|
|
131
|
-
function computeAvailabilityFromSnapshot(snapshot) {
|
|
132
|
-
const { phase } = snapshot.lifecycle;
|
|
133
|
-
if (phase === "idle" || phase === "detecting") return null;
|
|
134
|
-
if (phase === "terminated") {
|
|
135
|
-
const reason = snapshot.lifecycle.reason.kind;
|
|
136
|
-
if (reason === "not_embedded" || reason === "script_timeout") return false;
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
function useGristCore(options) {
|
|
142
|
-
const manager = useGristManager(options);
|
|
143
|
-
return useGristCoreFromManager(manager);
|
|
144
|
-
}
|
|
145
|
-
function useGristCoreFromManager(manager) {
|
|
146
|
-
const snapshot = useSyncExternalStore(
|
|
147
|
-
manager.subscribe,
|
|
148
|
-
manager.getSnapshot,
|
|
149
|
-
initialGristSnapshot
|
|
150
|
-
);
|
|
151
|
-
const status = deriveStatus(snapshot);
|
|
152
|
-
const error = deriveErrorMessage(snapshot);
|
|
153
|
-
const docApi = snapshot.lifecycle.phase === "online" && typeof grist !== "undefined" ? grist.docApi ?? null : null;
|
|
154
|
-
const isAvailable = computeAvailabilityFromSnapshot(snapshot);
|
|
155
|
-
const reload = useCallback(async () => {
|
|
156
|
-
manager.reload();
|
|
157
|
-
}, [manager]);
|
|
158
|
-
const capabilities = useMemo(() => deriveCapabilities(snapshot), [snapshot]);
|
|
159
|
-
return useMemo(
|
|
160
|
-
() => ({
|
|
161
|
-
status,
|
|
162
|
-
isAvailable,
|
|
163
|
-
isReady: status === "ready",
|
|
164
|
-
error,
|
|
165
|
-
docApi,
|
|
166
|
-
capabilities,
|
|
167
|
-
reload
|
|
168
|
-
}),
|
|
169
|
-
[status, isAvailable, error, docApi, capabilities, reload]
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// src/sdk/low-level-hooks/use-grist-ready.ts
|
|
174
|
-
function useGristReady(options) {
|
|
175
|
-
const { isAvailable, docApi, error, reload } = useGristCore(options);
|
|
176
|
-
return { isAvailable, docApi, error, reload };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// src/sdk/mid-level-hooks/use-grist-widget-options-from-context.ts
|
|
180
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState } from "react";
|
|
181
|
-
|
|
182
|
-
// src/sdk/internal/contexts.tsx
|
|
183
|
-
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
184
|
-
|
|
185
|
-
// src/sdk/internal/merge-grist-result.ts
|
|
186
|
-
function mergeGristResultFromSlices(slices) {
|
|
187
|
-
const { core, selection, currentTable, writes, reads, widgetOptionsApi, linking, attachmentsRest, theme, sectionApi, suppressAlerts, allowSelectBy } = slices;
|
|
188
|
-
return {
|
|
189
|
-
status: core.status,
|
|
190
|
-
isAvailable: core.isAvailable,
|
|
191
|
-
isReady: core.isReady,
|
|
192
|
-
error: core.error,
|
|
193
|
-
capabilities: core.capabilities,
|
|
194
|
-
record: selection.record,
|
|
195
|
-
records: selection.records,
|
|
196
|
-
mappedRecord: selection.mappedRecord,
|
|
197
|
-
mappings: selection.mappings,
|
|
198
|
-
recordsMappings: selection.recordsMappings,
|
|
199
|
-
columnMappingStatus: selection.columnMappingStatus,
|
|
200
|
-
mode: selection.mode,
|
|
201
|
-
resolveMappedColumnId: selection.resolveMappedColumnId,
|
|
202
|
-
mapBack: selection.mapBack,
|
|
203
|
-
mapBackSkipped: selection.mapBackSkipped,
|
|
204
|
-
isNewRecord: selection.isNewRecord,
|
|
205
|
-
newRecordMappings: selection.newRecordMappings,
|
|
206
|
-
currentTableId: currentTable.currentTableId,
|
|
207
|
-
currentTableLoading: currentTable.currentTableLoading,
|
|
208
|
-
refreshCurrentTable: currentTable.refreshCurrentTable,
|
|
209
|
-
table: writes.table,
|
|
210
|
-
getTable: writes.getTable,
|
|
211
|
-
actionStatus: writes.actionStatus,
|
|
212
|
-
actionError: writes.actionError,
|
|
213
|
-
applyActions: writes.applyActions,
|
|
214
|
-
reload: core.reload,
|
|
215
|
-
fetchTable: reads.fetchTable,
|
|
216
|
-
fetchTableRows: reads.fetchTableRows,
|
|
217
|
-
fetchRow: reads.fetchRow,
|
|
218
|
-
listTables: reads.listTables,
|
|
219
|
-
listColumns: reads.listColumns,
|
|
220
|
-
getDocName: reads.getDocName,
|
|
221
|
-
buildReplicaDocumentFromDocApi: reads.buildReplicaDocumentFromDocApi,
|
|
222
|
-
getAttachmentUrl: attachmentsRest.getAttachmentUrl,
|
|
223
|
-
fetchAttachmentBase64: attachmentsRest.fetchAttachmentBase64,
|
|
224
|
-
fetchAttachmentBlob: attachmentsRest.fetchAttachmentBlob,
|
|
225
|
-
widgetOptions: selection.widgetOptions,
|
|
226
|
-
widgetInteraction: selection.widgetInteraction,
|
|
227
|
-
getWidgetOptions: widgetOptionsApi.getWidgetOptions,
|
|
228
|
-
getWidgetOption: widgetOptionsApi.getWidgetOption,
|
|
229
|
-
setWidgetOption: widgetOptionsApi.setWidgetOption,
|
|
230
|
-
setWidgetOptions: widgetOptionsApi.setWidgetOptions,
|
|
231
|
-
patchWidgetOptions: widgetOptionsApi.patchWidgetOptions,
|
|
232
|
-
clearWidgetOptions: widgetOptionsApi.clearWidgetOptions,
|
|
233
|
-
fetchSelectedTable: reads.fetchSelectedTable,
|
|
234
|
-
fetchSelectedRecord: reads.fetchSelectedRecord,
|
|
235
|
-
readError: reads.readError ?? attachmentsRest.readError,
|
|
236
|
-
setCursorPosition: linking.setCursorPosition,
|
|
237
|
-
setLinkedRowSelection: linking.setLinkedRowSelection,
|
|
238
|
-
getAccessToken: attachmentsRest.getAccessToken,
|
|
239
|
-
fetchWithAuth: attachmentsRest.fetchWithAuth,
|
|
240
|
-
uploadAttachment: attachmentsRest.uploadAttachment,
|
|
241
|
-
theme: theme.theme,
|
|
242
|
-
configure: sectionApi.configure,
|
|
243
|
-
refreshMappings: sectionApi.refreshMappings,
|
|
244
|
-
suppressAlerts,
|
|
245
|
-
allowSelectBy
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// src/sdk/internal/contexts.tsx
|
|
250
|
-
var GristCoreContext = createContext2(null);
|
|
251
|
-
var GristSelectionContext = createContext2(null);
|
|
252
|
-
var GristCurrentTableContext = createContext2(null);
|
|
253
|
-
var GristWritesContext = createContext2(null);
|
|
254
|
-
var GristReadsContext = createContext2(null);
|
|
255
|
-
var GristWidgetOptionsApiContext = createContext2(null);
|
|
256
|
-
var GristLinkingContext = createContext2(null);
|
|
257
|
-
var GristAttachmentsRestContext = createContext2(null);
|
|
258
|
-
var GristThemeContext = createContext2(null);
|
|
259
|
-
var GristSectionApiContext = createContext2(null);
|
|
260
|
-
function useRequiredContext(ctx, name) {
|
|
261
|
-
const value = useContext2(ctx);
|
|
262
|
-
if (!value) {
|
|
263
|
-
throw new Error(`${name} must be used inside <GristWidgetProvider>`);
|
|
264
|
-
}
|
|
265
|
-
return value;
|
|
266
|
-
}
|
|
267
|
-
function useGristCoreContext() {
|
|
268
|
-
return useRequiredContext(GristCoreContext, "useGristCoreContext()");
|
|
269
|
-
}
|
|
270
|
-
function useGristSelectionContext() {
|
|
271
|
-
return useRequiredContext(GristSelectionContext, "useGristSelectionContext()");
|
|
272
|
-
}
|
|
273
|
-
function useGristCurrentTableContext() {
|
|
274
|
-
return useRequiredContext(GristCurrentTableContext, "useGristCurrentTableContext()");
|
|
275
|
-
}
|
|
276
|
-
function useGristWritesContext() {
|
|
277
|
-
return useRequiredContext(GristWritesContext, "useGristWritesContext()");
|
|
278
|
-
}
|
|
279
|
-
function useGristReadsContext() {
|
|
280
|
-
return useRequiredContext(GristReadsContext, "useGristReadsContext()");
|
|
281
|
-
}
|
|
282
|
-
function useGristWidgetOptionsApiContext() {
|
|
283
|
-
return useRequiredContext(GristWidgetOptionsApiContext, "useGristWidgetOptionsApiContext()");
|
|
284
|
-
}
|
|
285
|
-
function useGristLinkingContext() {
|
|
286
|
-
return useRequiredContext(GristLinkingContext, "useGristLinkingContext()");
|
|
287
|
-
}
|
|
288
|
-
function useGristAttachmentsRestContext() {
|
|
289
|
-
return useRequiredContext(GristAttachmentsRestContext, "useGristAttachmentsRestContext()");
|
|
290
|
-
}
|
|
291
|
-
function useGristThemeContext() {
|
|
292
|
-
return useRequiredContext(GristThemeContext, "useGristThemeContext()");
|
|
293
|
-
}
|
|
294
|
-
function useGristSectionApiContext() {
|
|
295
|
-
return useRequiredContext(GristSectionApiContext, "useGristSectionApiContext()");
|
|
296
|
-
}
|
|
297
|
-
function useGristFromProvider() {
|
|
298
|
-
const core = useGristCoreContext();
|
|
299
|
-
const selection = useGristSelectionContext();
|
|
300
|
-
const currentTable = useGristCurrentTableContext();
|
|
301
|
-
const writes = useGristWritesContext();
|
|
302
|
-
const reads = useGristReadsContext();
|
|
303
|
-
const widgetOptionsApi = useGristWidgetOptionsApiContext();
|
|
304
|
-
const linking = useGristLinkingContext();
|
|
305
|
-
const attachmentsRest = useGristAttachmentsRestContext();
|
|
306
|
-
const theme = useGristThemeContext();
|
|
307
|
-
const sectionApi = useGristSectionApiContext();
|
|
308
|
-
return useMemo2(
|
|
309
|
-
() => mergeGristResultFromSlices({
|
|
310
|
-
core,
|
|
311
|
-
selection,
|
|
312
|
-
currentTable,
|
|
313
|
-
writes,
|
|
314
|
-
reads,
|
|
315
|
-
widgetOptionsApi,
|
|
316
|
-
linking,
|
|
317
|
-
attachmentsRest,
|
|
318
|
-
theme,
|
|
319
|
-
sectionApi
|
|
320
|
-
}),
|
|
321
|
-
[
|
|
322
|
-
core,
|
|
323
|
-
selection,
|
|
324
|
-
currentTable,
|
|
325
|
-
writes,
|
|
326
|
-
reads,
|
|
327
|
-
widgetOptionsApi,
|
|
328
|
-
linking,
|
|
329
|
-
attachmentsRest,
|
|
330
|
-
theme,
|
|
331
|
-
sectionApi
|
|
332
|
-
]
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// src/sdk/mid-level-hooks/use-grist-widget-options-from-context.ts
|
|
337
|
-
function mergeWithDefaults(defaults, stored, namespace) {
|
|
338
|
-
if (stored === null || stored === void 0) return { ...defaults };
|
|
339
|
-
const source = namespace ? stored[namespace] : stored;
|
|
340
|
-
if (!source || typeof source !== "object") return { ...defaults };
|
|
341
|
-
return { ...defaults, ...source };
|
|
342
|
-
}
|
|
343
|
-
function useGristWidgetOptionsFromContext(config) {
|
|
344
|
-
const debounceMs = config.debounceMs ?? 250;
|
|
345
|
-
const selection = useGristSelectionContext();
|
|
346
|
-
const optionsApi = useGristWidgetOptionsApiContext();
|
|
347
|
-
const defaultsRef = useRef2(config.defaults);
|
|
348
|
-
useEffect2(() => {
|
|
349
|
-
defaultsRef.current = config.defaults;
|
|
350
|
-
}, [config.defaults]);
|
|
351
|
-
const rawOptions = selection.widgetOptions;
|
|
352
|
-
const hasReceived = rawOptions !== null;
|
|
353
|
-
const merged = useMemo3(
|
|
354
|
-
() => mergeWithDefaults(defaultsRef.current, rawOptions, config.namespace),
|
|
355
|
-
[rawOptions, config.namespace]
|
|
356
|
-
);
|
|
357
|
-
const [localOverride, setLocalOverride] = useState(null);
|
|
358
|
-
const options = localOverride ?? merged;
|
|
359
|
-
useEffect2(() => {
|
|
360
|
-
setLocalOverride(null);
|
|
361
|
-
}, [rawOptions]);
|
|
362
|
-
const debounceRef = useRef2(null);
|
|
363
|
-
const flushWrite = useCallback2(
|
|
364
|
-
async (value) => {
|
|
365
|
-
if (config.namespace) {
|
|
366
|
-
await optionsApi.setWidgetOption(config.namespace, value);
|
|
367
|
-
} else {
|
|
368
|
-
await optionsApi.setWidgetOptions({ ...value });
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
[config.namespace, optionsApi]
|
|
372
|
-
);
|
|
373
|
-
const scheduleWrite = useCallback2(
|
|
374
|
-
(value) => {
|
|
375
|
-
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
376
|
-
debounceRef.current = window.setTimeout(() => {
|
|
377
|
-
debounceRef.current = null;
|
|
378
|
-
void flushWrite(value);
|
|
379
|
-
}, debounceMs);
|
|
380
|
-
},
|
|
381
|
-
[debounceMs, flushWrite]
|
|
382
|
-
);
|
|
383
|
-
useEffect2(() => {
|
|
384
|
-
return () => {
|
|
385
|
-
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
386
|
-
};
|
|
387
|
-
}, []);
|
|
388
|
-
const setOptions = useCallback2(
|
|
389
|
-
(next) => {
|
|
390
|
-
setLocalOverride(next);
|
|
391
|
-
scheduleWrite(next);
|
|
392
|
-
},
|
|
393
|
-
[scheduleWrite]
|
|
394
|
-
);
|
|
395
|
-
const patchOptions = useCallback2(
|
|
396
|
-
(patch) => {
|
|
397
|
-
setLocalOverride((prev) => {
|
|
398
|
-
const base = prev ?? merged;
|
|
399
|
-
const next = { ...base, ...patch };
|
|
400
|
-
scheduleWrite(next);
|
|
401
|
-
return next;
|
|
402
|
-
});
|
|
403
|
-
},
|
|
404
|
-
[merged, scheduleWrite]
|
|
405
|
-
);
|
|
406
|
-
const reset = useCallback2(async () => {
|
|
407
|
-
if (debounceRef.current) {
|
|
408
|
-
window.clearTimeout(debounceRef.current);
|
|
409
|
-
debounceRef.current = null;
|
|
410
|
-
}
|
|
411
|
-
const next = { ...defaultsRef.current };
|
|
412
|
-
setLocalOverride(null);
|
|
413
|
-
await flushWrite(next);
|
|
414
|
-
}, [flushWrite]);
|
|
415
|
-
return useMemo3(
|
|
416
|
-
() => ({
|
|
417
|
-
options,
|
|
418
|
-
loading: !hasReceived,
|
|
419
|
-
setOptions,
|
|
420
|
-
patchOptions,
|
|
421
|
-
reset
|
|
422
|
-
}),
|
|
423
|
-
[options, hasReceived, setOptions, patchOptions, reset]
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// src/sdk/internal/hooks/use-rpc-heartbeat-coalesce.ts
|
|
428
|
-
import { useCallback as useCallback3 } from "react";
|
|
429
|
-
function useRpcHeartbeatCoalesce() {
|
|
430
|
-
const manager = useGristManagerOptional();
|
|
431
|
-
return useCallback3(
|
|
432
|
-
async (fn) => {
|
|
433
|
-
const result = await fn();
|
|
434
|
-
manager?.recordRpcSuccess();
|
|
435
|
-
return result;
|
|
436
|
-
},
|
|
437
|
-
[manager]
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
export {
|
|
442
|
-
getGristAccessToken,
|
|
443
|
-
fetchWithGristAuth,
|
|
444
|
-
buildManagerOptionsFromGristOptions,
|
|
445
|
-
GristHandshakeManagerContext,
|
|
446
|
-
useGristManagerOptional,
|
|
447
|
-
useGristManager,
|
|
448
|
-
mergeGristResultFromSlices,
|
|
449
|
-
GristCoreContext,
|
|
450
|
-
GristSelectionContext,
|
|
451
|
-
GristCurrentTableContext,
|
|
452
|
-
GristWritesContext,
|
|
453
|
-
GristReadsContext,
|
|
454
|
-
GristWidgetOptionsApiContext,
|
|
455
|
-
GristLinkingContext,
|
|
456
|
-
GristAttachmentsRestContext,
|
|
457
|
-
GristThemeContext,
|
|
458
|
-
GristSectionApiContext,
|
|
459
|
-
useGristCoreContext,
|
|
460
|
-
useGristSelectionContext,
|
|
461
|
-
useGristCurrentTableContext,
|
|
462
|
-
useGristWritesContext,
|
|
463
|
-
useGristThemeContext,
|
|
464
|
-
useGristFromProvider,
|
|
465
|
-
useGristCore,
|
|
466
|
-
useRpcHeartbeatCoalesce,
|
|
467
|
-
useGristReady,
|
|
468
|
-
useGristWidgetOptionsFromContext
|
|
469
|
-
};
|
|
470
|
-
//# sourceMappingURL=chunk-EPEBKXVN.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sdk/helpers/grist-fetch-with-auth.ts","../src/sdk/internal/hooks/use-grist-core.ts","../src/sdk/internal/handshake-manager-context.ts","../src/sdk/internal/build-manager-options.ts","../src/sdk/low-level-hooks/use-grist-ready.ts","../src/sdk/mid-level-hooks/use-grist-widget-options-from-context.ts","../src/sdk/internal/contexts.tsx","../src/sdk/internal/merge-grist-result.ts","../src/sdk/internal/hooks/use-rpc-heartbeat-coalesce.ts"],"sourcesContent":["import type { GristDocApi } from \"../types\"\n\nexport type GristAccessToken = {\n token: string\n baseUrl: string\n}\n\ntype TokenCacheEntry = {\n token: GristAccessToken\n fetchedAt: number\n}\n\nconst TOKEN_TTL_MS = 45_000\nconst cache = new Map<string, TokenCacheEntry>()\nconst inFlight = new Map<string, Promise<GristAccessToken>>()\n\nfunction cacheKey(readOnly: boolean): string {\n return readOnly ? \"ro\" : \"rw\"\n}\n\n/**\n * Returns an access token suitable for calling Grist's REST API directly.\n * Tokens are short-lived; results are cached briefly and coalesced per readOnly flag.\n */\nexport async function getGristAccessToken(\n docApi: GristDocApi,\n options?: { readOnly?: boolean }\n): Promise<GristAccessToken> {\n if (!docApi.getAccessToken) {\n throw new Error(\"getAccessToken is not available on docApi (need higher access level)\")\n }\n\n const readOnly = options?.readOnly ?? false\n const key = cacheKey(readOnly)\n const now = Date.now()\n const hit = cache.get(key)\n if (hit && now - hit.fetchedAt < TOKEN_TTL_MS) {\n return hit.token\n }\n\n const pending = inFlight.get(key)\n if (pending) return pending\n\n const next = docApi.getAccessToken({ readOnly }).then((token) => {\n cache.set(key, { token, fetchedAt: Date.now() })\n inFlight.delete(key)\n return token\n })\n\n inFlight.set(key, next)\n return next\n}\n\n/** Clear cached tokens (tests or after auth errors). */\nexport function clearGristAccessTokenCache(): void {\n cache.clear()\n inFlight.clear()\n}\n\n/**\n * `fetch()` against Grist's REST API with the doc access token attached.\n */\nexport async function fetchWithGristAuth(\n docApi: GristDocApi,\n path: string,\n init?: RequestInit & { readOnly?: boolean }\n): Promise<Response> {\n const readOnly = init?.readOnly ?? false\n let token: GristAccessToken\n try {\n token = await getGristAccessToken(docApi, { readOnly })\n } catch (err) {\n clearGristAccessTokenCache()\n throw err\n }\n\n const isAbsolute = /^https?:\\/\\//i.test(path)\n const cleanPath = isAbsolute ? path : path.startsWith(\"/\") ? path : `/${path}`\n\n const url = isAbsolute\n ? new URL(path)\n : new URL(`${token.baseUrl.replace(/\\/$/, \"\")}${cleanPath}`)\n\n // Grist signs widget REST calls with a short-lived `auth` query parameter.\n // Do not send `Authorization: Bearer` from the browser: many Grist deployments\n // omit it from Access-Control-Allow-Headers, which breaks POST (e.g. attachment upload).\n if (!url.searchParams.has(\"auth\")) url.searchParams.set(\"auth\", token.token)\n\n const headers = new Headers(init?.headers ?? {})\n headers.delete(\"Authorization\")\n\n const response = await fetch(url.toString(), { ...init, headers })\n if (response.status === 401) {\n clearGristAccessTokenCache()\n const retryToken = await getGristAccessToken(docApi, { readOnly })\n url.searchParams.set(\"auth\", retryToken.token)\n return fetch(url.toString(), { ...init, headers })\n }\n return response\n}\n","import { useCallback, useMemo, useSyncExternalStore } from \"react\"\n\nimport type { GristDocApi, GristReadyOptions } from \"../../types\"\nimport {\n deriveCapabilities,\n deriveErrorMessage,\n deriveStatus,\n initialGristSnapshot,\n type GristCapabilities,\n type GristWidgetSnapshot,\n type GristWidgetStatus,\n} from \"../handshake\"\nimport type { ManagerControls } from \"../handshake/manager\"\nimport { useGristManager } from \"../handshake-manager-context\"\n\n// Re-exported, not aliased: a second `GristWidgetStatus` symbol would leave\n// `grist-widget-sdk/advanced` (whose handshake hooks return the original)\n// referencing a type no public entry exports.\nexport type { GristWidgetStatus }\n\nexport type UseGristCoreOptions = GristReadyOptions & {\n availabilityMaxAttempts?: number\n availabilityIntervalMs?: number\n logWhenNotEmbedded?: boolean\n}\n\nexport type UseGristCoreResult = {\n status: GristWidgetStatus\n isAvailable: boolean | null\n isReady: boolean\n error: string | null\n docApi: GristDocApi | null\n /** Derived from the handshake snapshot — same values as `useGristCapabilities()` on `/advanced`. */\n capabilities: GristCapabilities\n reload: () => Promise<void>\n}\n\n/** Project handshake availability for legacy `isAvailable` consumers. */\nexport function computeAvailabilityFromSnapshot(\n snapshot: GristWidgetSnapshot\n): boolean | null {\n const { phase } = snapshot.lifecycle\n if (phase === \"idle\" || phase === \"detecting\") return null\n if (phase === \"terminated\") {\n const reason = snapshot.lifecycle.reason.kind\n if (reason === \"not_embedded\" || reason === \"script_timeout\") return false\n return true\n }\n return true\n}\n\n/**\n * Core connectivity hook — always backed by {@link GristHandshakeManager}.\n */\nexport function useGristCore(options?: UseGristCoreOptions): UseGristCoreResult {\n const manager = useGristManager(options)\n return useGristCoreFromManager(manager)\n}\n\n/**\n * Subscribe to an existing manager and project its snapshot into\n * {@link UseGristCoreResult}.\n */\nexport function useGristCoreFromManager(manager: ManagerControls): UseGristCoreResult {\n const snapshot = useSyncExternalStore(\n manager.subscribe,\n manager.getSnapshot,\n initialGristSnapshot\n )\n const status = deriveStatus(snapshot)\n const error = deriveErrorMessage(snapshot)\n // Read on every render, not memoized: `window.grist` is a global React\n // cannot track (the emulator installs and removes it), so any memo key\n // could leave it stale; and `grist.docApi` is a stable reference, so a\n // fresh read keeps the same identity for as long as the global does.\n const docApi: GristDocApi | null =\n snapshot.lifecycle.phase === \"online\" && typeof grist !== \"undefined\"\n ? (grist.docApi ?? null)\n : null\n const isAvailable = computeAvailabilityFromSnapshot(snapshot)\n const reload = useCallback(async () => {\n manager.reload()\n }, [manager])\n const capabilities = useMemo(() => deriveCapabilities(snapshot), [snapshot])\n return useMemo(\n () => ({\n status,\n isAvailable,\n isReady: status === \"ready\",\n error,\n docApi,\n capabilities,\n reload,\n }),\n [status, isAvailable, error, docApi, capabilities, reload]\n )\n}\n","/**\n * Internal context that hosts the shared {@link GristHandshakeManager} for a\n * widget subtree.\n *\n * - `<GristWidgetProvider>` publishes the manager via\n * {@link GristHandshakeManagerContext}.\n * - Standalone `useGrist()` / mid-level hooks without a provider acquire the\n * page-level embedded manager ({@link acquireEmbeddedHandshakeManager}).\n *\n * All SDK hooks call {@link useGristManager} — there is no pre-FSM fallback.\n */\n\nimport { createContext, useContext, useEffect, useRef } from \"react\"\n\nimport { buildManagerOptionsFromGristOptions, type BuildManagerOptionsInput } from \"./build-manager-options\"\nimport {\n acquireEmbeddedHandshakeManager,\n releaseEmbeddedHandshakeManager,\n} from \"./embedded-handshake-manager\"\nimport type { ManagerControls } from \"./handshake/manager\"\n\nexport const GristHandshakeManagerContext = createContext<ManagerControls | null>(null)\n\n/** Read the manager from context. Returns `null` outside a provider. */\nexport function useGristManagerOptional(): ManagerControls | null {\n return useContext(GristHandshakeManagerContext)\n}\n\n/**\n * Returns the active handshake manager for this React tree.\n *\n * Inside `<GristWidgetProvider>` this is the provider's manager. Otherwise a\n * single ref-counted embedded manager is shared across all standalone hook\n * instances on the page.\n */\nexport function useGristManager(options?: BuildManagerOptionsInput): ManagerControls {\n const fromProvider = useContext(GristHandshakeManagerContext)\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const embeddedRef = useRef<ManagerControls | null>(null)\n const ownsEmbeddedRef = useRef(false)\n\n // Same deliberate exception as useGrist() above: whether a manager comes\n // from a provider is fixed per mounted instance, not a per-render\n // condition -- the early return below is structurally invariant.\n if (fromProvider) {\n return fromProvider\n }\n\n if (embeddedRef.current === null) {\n embeddedRef.current = acquireEmbeddedHandshakeManager(\n buildManagerOptionsFromGristOptions(optionsRef.current)\n )\n ownsEmbeddedRef.current = true\n }\n\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useEffect(() => {\n if (!ownsEmbeddedRef.current) return\n return () => {\n releaseEmbeddedHandshakeManager()\n embeddedRef.current = null\n ownsEmbeddedRef.current = false\n }\n }, [])\n\n return embeddedRef.current\n}\n","import type { ManagerOptions } from \"./handshake/manager\"\nimport { ensureGristReady, resetGristReadySingleton } from \"./ready-singleton\"\nimport type { GristReadyOptions } from \"../types\"\n\nexport type BuildManagerOptionsInput = GristReadyOptions & {\n availabilityMaxAttempts?: number\n availabilityIntervalMs?: number\n logWhenNotEmbedded?: boolean\n}\n\n/**\n * Map public widget options to {@link ManagerOptions} for\n * {@link createGristHandshakeManager}.\n */\nexport function buildManagerOptionsFromGristOptions(\n options?: BuildManagerOptionsInput\n): ManagerOptions {\n const opts: ManagerOptions = {\n requiredAccess: options?.requiredAccess ?? \"read table\",\n columns: options?.columns ?? [],\n allowSelectBy: options?.allowSelectBy,\n hasCustomOptions: options?.hasCustomOptions,\n onEditOptions: options?.onEditOptions,\n negotiate: {\n readyImpl: (payload) => ensureGristReady(payload),\n },\n onBeforeReload: () => resetGristReadySingleton(),\n }\n if (\n options?.availabilityMaxAttempts !== undefined ||\n options?.availabilityIntervalMs !== undefined\n ) {\n const interval = options?.availabilityIntervalMs ?? 1000\n const max = options?.availabilityMaxAttempts ?? 10\n opts.detect = {\n pollSequenceMs: Array.from({ length: max }, () => interval),\n budgetMs: interval * max,\n }\n }\n return opts\n}\n","import { useGristCore } from \"../internal/hooks/use-grist-core\"\nimport type { GristDocApi, GristReadyOptions } from \"../types\"\n\nexport type UseGristReadyOptions = GristReadyOptions & {\n availabilityMaxAttempts?: number\n availabilityIntervalMs?: number\n logWhenNotEmbedded?: boolean\n}\n\nexport type UseGristReadyResult = {\n isAvailable: boolean | null\n docApi: GristDocApi | null\n error: string | null\n reload: () => Promise<void>\n}\n\n/**\n * Handshake-backed `grist.ready` — delegates to {@link useGristCore} (FSM).\n *\n * Prefer `<GristWidgetProvider>` + `useGrist()` for full widget state; use this\n * hook when you only need `docApi` and connection status.\n */\nexport function useGristReady(options?: UseGristReadyOptions): UseGristReadyResult {\n const { isAvailable, docApi, error, reload } = useGristCore(options)\n return { isAvailable, docApi, error, reload }\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\n\nimport {\n useGristSelectionContext,\n useGristWidgetOptionsApiContext,\n} from \"../internal/contexts\"\n\nexport type UseGristWidgetOptionsFromContextConfig<T> = {\n /** Default values returned before Grist responds and merged with stored options. */\n defaults: T\n /** Debounce window for `setOptions` writes (ms). Default: 250. */\n debounceMs?: number\n /**\n * Optional namespace key. When set, this hook reads/writes\n * `options[namespace]` instead of the whole options blob.\n */\n namespace?: string\n}\n\nexport type UseGristWidgetOptionsFromContextResult<T> = {\n options: T\n /** True until Grist has responded with the initial `onOptions` payload. */\n loading: boolean\n /** Replace the options object (debounced write to Grist). */\n setOptions: (next: T) => void\n /** Patch a subset of keys (debounced write to Grist). */\n patchOptions: (patch: Partial<T>) => void\n /** Reset to `defaults` (immediate write to Grist). */\n reset: () => Promise<void>\n}\n\nfunction mergeWithDefaults<T>(defaults: T, stored: unknown, namespace?: string): T {\n if (stored === null || stored === undefined) return { ...defaults }\n const source = (namespace ? (stored as Record<string, unknown>)[namespace] : stored) as Partial<T> | undefined\n if (!source || typeof source !== \"object\") return { ...defaults }\n return { ...defaults, ...source }\n}\n\n/**\n * Typed widget options hook designed for use **inside a `<GristWidgetProvider>`**.\n *\n * Unlike `useGristWidgetOptions()` (which calls `useGristReady()` and conflicts\n * with the provider), this hook reads from the provider's existing context —\n * `widgetOptions` for reactive reads, and `patchWidgetOptions` / `setWidgetOptions`\n * for writes.\n *\n * ```tsx\n * function MyConfigPanel() {\n * const { options, patchOptions } = useGristWidgetOptionsFromContext({\n * defaults: { color: '#FF0000', showLabels: true },\n * })\n * return <input value={options.color} onChange={e => patchOptions({ color: e.target.value })} />\n * }\n * ```\n */\nexport function useGristWidgetOptionsFromContext<T extends Record<string, unknown>>(\n config: UseGristWidgetOptionsFromContextConfig<T>\n): UseGristWidgetOptionsFromContextResult<T> {\n const debounceMs = config.debounceMs ?? 250\n const selection = useGristSelectionContext()\n const optionsApi = useGristWidgetOptionsApiContext()\n\n const defaultsRef = useRef(config.defaults)\n useEffect(() => {\n defaultsRef.current = config.defaults\n }, [config.defaults])\n\n const rawOptions = selection.widgetOptions\n const hasReceived = rawOptions !== null\n\n const merged = useMemo(\n () => mergeWithDefaults(defaultsRef.current, rawOptions, config.namespace),\n [rawOptions, config.namespace]\n )\n\n const [localOverride, setLocalOverride] = useState<T | null>(null)\n const options = localOverride ?? merged\n\n useEffect(() => {\n setLocalOverride(null)\n }, [rawOptions])\n\n const debounceRef = useRef<number | null>(null)\n\n const flushWrite = useCallback(\n async (value: T) => {\n if (config.namespace) {\n await optionsApi.setWidgetOption(config.namespace, value)\n } else {\n await optionsApi.setWidgetOptions({ ...value })\n }\n },\n [config.namespace, optionsApi]\n )\n\n const scheduleWrite = useCallback(\n (value: T) => {\n if (debounceRef.current) window.clearTimeout(debounceRef.current)\n debounceRef.current = window.setTimeout(() => {\n debounceRef.current = null\n void flushWrite(value)\n }, debounceMs)\n },\n [debounceMs, flushWrite]\n )\n\n useEffect(() => {\n return () => {\n if (debounceRef.current) window.clearTimeout(debounceRef.current)\n }\n }, [])\n\n const setOptions = useCallback(\n (next: T) => {\n setLocalOverride(next)\n scheduleWrite(next)\n },\n [scheduleWrite]\n )\n\n const patchOptions = useCallback(\n (patch: Partial<T>) => {\n setLocalOverride((prev) => {\n const base = prev ?? merged\n const next = { ...base, ...patch }\n scheduleWrite(next)\n return next\n })\n },\n [merged, scheduleWrite]\n )\n\n const reset = useCallback(async () => {\n if (debounceRef.current) {\n window.clearTimeout(debounceRef.current)\n debounceRef.current = null\n }\n const next = { ...defaultsRef.current }\n setLocalOverride(null)\n await flushWrite(next)\n }, [flushWrite])\n\n return useMemo(\n () => ({\n options,\n loading: !hasReceived,\n setOptions,\n patchOptions,\n reset,\n }),\n [options, hasReceived, setOptions, patchOptions, reset]\n )\n}\n","import { createContext, useContext, useMemo, type Context } from \"react\"\n\nimport type { ComposedGristWidgetSlices } from \"./compose-grist-widget\"\nimport { mergeGristResultFromSlices } from \"./merge-grist-result\"\nimport type { UseGristResult } from \"../use-grist-types\"\nimport type { UseGristAttachmentsRestResult } from \"./hooks/use-grist-attachments-rest\"\nimport type { UseGristCoreResult } from \"./hooks/use-grist-core\"\nimport type { UseGristCurrentTableResult } from \"./hooks/use-grist-current-table\"\nimport type { UseGristLinkingResult } from \"./hooks/use-grist-linking\"\nimport type { UseGristReadsResult } from \"./hooks/use-grist-reads\"\nimport type { UseGristSectionApiResult } from \"./hooks/use-grist-section-api\"\nimport type { UseGristSelectionResult } from \"./hooks/use-grist-selection\"\nimport type { UseGristTableOpsBoundResult } from \"./hooks/use-grist-table-ops-bound\"\nimport type { UseGristThemeResult } from \"./hooks/use-grist-theme\"\nimport type { UseGristWidgetOptionsApiResult } from \"./hooks/use-grist-widget-options-api\"\n\nexport type GristCoreContextValue = UseGristCoreResult & {\n tableError: string | null\n}\n\nexport type GristStatusSliceValue = GristCoreContextValue & UseGristCurrentTableResult\n\nexport const GristCoreContext = createContext<GristCoreContextValue | null>(null)\nexport const GristSelectionContext = createContext<UseGristSelectionResult | null>(null)\nexport const GristCurrentTableContext = createContext<UseGristCurrentTableResult | null>(null)\nexport const GristWritesContext = createContext<UseGristTableOpsBoundResult | null>(null)\nexport const GristReadsContext = createContext<UseGristReadsResult | null>(null)\nexport const GristWidgetOptionsApiContext = createContext<UseGristWidgetOptionsApiResult | null>(null)\nexport const GristLinkingContext = createContext<UseGristLinkingResult | null>(null)\nexport const GristAttachmentsRestContext = createContext<UseGristAttachmentsRestResult | null>(null)\nexport const GristThemeContext = createContext<UseGristThemeResult | null>(null)\nexport const GristSectionApiContext = createContext<UseGristSectionApiResult | null>(null)\n\nfunction useRequiredContext<T>(ctx: Context<T | null>, name: string): T {\n const value = useContext(ctx)\n if (!value) {\n throw new Error(`${name} must be used inside <GristWidgetProvider>`)\n }\n return value\n}\n\nexport function useGristCoreContext(): GristCoreContextValue {\n return useRequiredContext(GristCoreContext, \"useGristCoreContext()\")\n}\n\nexport function useGristSelectionContext<\n TRow = Record<string, unknown>,\n TMapped = Record<string, unknown>,\n>(): UseGristSelectionResult<TRow, TMapped> {\n return useRequiredContext(GristSelectionContext, \"useGristSelectionContext()\") as UseGristSelectionResult<\n TRow,\n TMapped\n >\n}\n\nexport function useGristCurrentTableContext(): UseGristCurrentTableResult {\n return useRequiredContext(GristCurrentTableContext, \"useGristCurrentTableContext()\")\n}\n\nexport function useGristWritesContext(): UseGristTableOpsBoundResult {\n return useRequiredContext(GristWritesContext, \"useGristWritesContext()\")\n}\n\nexport function useGristReadsContext(): UseGristReadsResult {\n return useRequiredContext(GristReadsContext, \"useGristReadsContext()\")\n}\n\nexport function useGristWidgetOptionsApiContext(): UseGristWidgetOptionsApiResult {\n return useRequiredContext(GristWidgetOptionsApiContext, \"useGristWidgetOptionsApiContext()\")\n}\n\nexport function useGristLinkingContext(): UseGristLinkingResult {\n return useRequiredContext(GristLinkingContext, \"useGristLinkingContext()\")\n}\n\nexport function useGristAttachmentsRestContext(): UseGristAttachmentsRestResult {\n return useRequiredContext(GristAttachmentsRestContext, \"useGristAttachmentsRestContext()\")\n}\n\nexport function useGristThemeContext(): UseGristThemeResult {\n return useRequiredContext(GristThemeContext, \"useGristThemeContext()\")\n}\n\nexport function useGristSectionApiContext(): UseGristSectionApiResult {\n return useRequiredContext(GristSectionApiContext, \"useGristSectionApiContext()\")\n}\n\n/**\n * Live merge of all provider slices — used by `useGrist()` / `useGristContext()` so\n * cursor-driven `onRecord` updates are not lost behind a stale memoized snapshot.\n */\nexport function useGristFromProvider<\n TRow = Record<string, unknown>,\n TMapped = Record<string, unknown>,\n>(): UseGristResult<TRow, TMapped> {\n const core = useGristCoreContext()\n const selection = useGristSelectionContext<TRow, TMapped>()\n const currentTable = useGristCurrentTableContext()\n const writes = useGristWritesContext()\n const reads = useGristReadsContext()\n const widgetOptionsApi = useGristWidgetOptionsApiContext()\n const linking = useGristLinkingContext()\n const attachmentsRest = useGristAttachmentsRestContext()\n const theme = useGristThemeContext()\n const sectionApi = useGristSectionApiContext()\n\n return useMemo(\n () =>\n mergeGristResultFromSlices<TRow, TMapped>({\n core,\n selection,\n currentTable,\n writes,\n reads,\n widgetOptionsApi,\n linking,\n attachmentsRest,\n theme,\n sectionApi,\n } as ComposedGristWidgetSlices),\n [\n core,\n selection,\n currentTable,\n writes,\n reads,\n widgetOptionsApi,\n linking,\n attachmentsRest,\n theme,\n sectionApi,\n ]\n )\n}\n","import type { ComposedGristWidgetSlices } from \"./compose-grist-widget\"\nimport type { UseGristResult } from \"../use-grist-types\"\n\nexport function mergeGristResultFromSlices<TRow = Record<string, unknown>, TMapped = Record<string, unknown>>(\n slices: ComposedGristWidgetSlices\n): UseGristResult<TRow, TMapped> {\n const { core, selection, currentTable, writes, reads, widgetOptionsApi, linking, attachmentsRest, theme, sectionApi, suppressAlerts, allowSelectBy } =\n slices\n return {\n status: core.status,\n isAvailable: core.isAvailable,\n isReady: core.isReady,\n error: core.error,\n capabilities: core.capabilities,\n record: selection.record as UseGristResult<TRow, TMapped>[\"record\"],\n records: selection.records as UseGristResult<TRow, TMapped>[\"records\"],\n mappedRecord: selection.mappedRecord as TMapped | null,\n mappings: selection.mappings,\n recordsMappings: selection.recordsMappings,\n columnMappingStatus: selection.columnMappingStatus,\n mode: selection.mode,\n resolveMappedColumnId: selection.resolveMappedColumnId,\n mapBack: selection.mapBack as (patch: Partial<TMapped>) => Record<string, unknown>,\n mapBackSkipped: selection.mapBackSkipped,\n isNewRecord: selection.isNewRecord,\n newRecordMappings: selection.newRecordMappings,\n currentTableId: currentTable.currentTableId,\n currentTableLoading: currentTable.currentTableLoading,\n refreshCurrentTable: currentTable.refreshCurrentTable,\n table: writes.table,\n getTable: writes.getTable,\n actionStatus: writes.actionStatus,\n actionError: writes.actionError,\n applyActions: writes.applyActions,\n reload: core.reload,\n fetchTable: reads.fetchTable,\n fetchTableRows: reads.fetchTableRows,\n fetchRow: reads.fetchRow,\n listTables: reads.listTables,\n listColumns: reads.listColumns,\n getDocName: reads.getDocName,\n buildReplicaDocumentFromDocApi: reads.buildReplicaDocumentFromDocApi,\n getAttachmentUrl: attachmentsRest.getAttachmentUrl,\n fetchAttachmentBase64: attachmentsRest.fetchAttachmentBase64,\n fetchAttachmentBlob: attachmentsRest.fetchAttachmentBlob,\n widgetOptions: selection.widgetOptions,\n widgetInteraction: selection.widgetInteraction,\n getWidgetOptions: widgetOptionsApi.getWidgetOptions,\n getWidgetOption: widgetOptionsApi.getWidgetOption,\n setWidgetOption: widgetOptionsApi.setWidgetOption,\n setWidgetOptions: widgetOptionsApi.setWidgetOptions,\n patchWidgetOptions: widgetOptionsApi.patchWidgetOptions,\n clearWidgetOptions: widgetOptionsApi.clearWidgetOptions,\n fetchSelectedTable: reads.fetchSelectedTable,\n fetchSelectedRecord: reads.fetchSelectedRecord,\n readError: reads.readError ?? attachmentsRest.readError,\n setCursorPosition: linking.setCursorPosition,\n setLinkedRowSelection: linking.setLinkedRowSelection,\n getAccessToken: attachmentsRest.getAccessToken,\n fetchWithAuth: attachmentsRest.fetchWithAuth,\n uploadAttachment: attachmentsRest.uploadAttachment,\n theme: theme.theme,\n configure: sectionApi.configure,\n refreshMappings: sectionApi.refreshMappings,\n suppressAlerts,\n allowSelectBy,\n }\n}\n","/**\n * `useRpcHeartbeatCoalesce()` — coalesce real RPC traffic with the handshake\n * heartbeat.\n *\n * When a {@link GristHandshakeManager} is mounted (via `<GristWidgetProvider>`\n * or `<GristHandshakeProvider>`), every successful Grist RPC call is treated\n * as a natural heartbeat: the manager's `recordRpcSuccess()` resets the\n * missed-ping counter and pushes the next probe out by a full interval, so\n * a chatty widget never burns dedicated heartbeat probes.\n *\n * Failures are NOT coalesced: semantic errors (validation, \"row not found\",\n * \"no such column\") would briefly flash a connection-degraded indicator even\n * though the channel is alive. Instead we let the regular heartbeat probe\n * detect real transport failures on its own schedule (~30 s). The user\n * already sees the action error immediately, so the heartbeat indicator\n * doesn't need to race.\n *\n * Outside a provider (no manager in context), the wrapper is a zero-cost\n * passthrough — every slice hook keeps working exactly like the pre-FSM\n * release.\n */\n\nimport { useCallback } from \"react\"\n\nimport { useGristManagerOptional } from \"../handshake-manager-context\"\n\nexport type RpcHeartbeatCoalesce = <T>(fn: () => Promise<T>) => Promise<T>\n\nexport function useRpcHeartbeatCoalesce(): RpcHeartbeatCoalesce {\n const manager = useGristManagerOptional()\n return useCallback<RpcHeartbeatCoalesce>(\n async (fn) => {\n // No catch here, deliberately -- see the module docstring: failures\n // are not coalesced, so a rejection just propagates as-is.\n const result = await fn()\n manager?.recordRpcSuccess()\n return result\n },\n [manager]\n )\n}\n"],"mappings":"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","names":["useCallback","useEffect","useMemo","useRef","createContext","useContext","useMemo","createContext","useContext","useMemo","useRef","useEffect","useMemo","useCallback","useCallback","useCallback"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sdk/helpers/grist-table-data.ts"],"sourcesContent":["import type { GristDocApi, GristReplicaColumn, GristRowRecord } from \"../types\"\nimport { decodeGristValue } from \"./grist-value-codec\"\nimport {\n type GristSafeParseCellResult,\n type GristSafeTableCellMeta,\n safeParseGristTableData,\n type GristSafeTableCellIssue,\n type SafeParseGristTableDataOptions,\n} from \"./grist-safe-parse\"\n\n/**\n * Column-oriented table payload from `grist.docApi.fetchTable`.\n */\nexport type GristColumnarTable = Record<string, unknown[]>\nexport type GristTableRowsOrder = \"none\" | \"id\" | \"grist\"\nexport type FetchGristTableRowsOptions = {\n /** Optional schema metadata map to decode cells (except `id`) with `decodeGristValue`. */\n columns?: Record<string, GristReplicaColumn>\n /**\n * Row ordering strategy.\n * - `grist` (default): `manualSort` ascending when present, fallback `id`.\n * - `id`: numeric `id` ascending.\n * - `none`: preserve raw `fetchTable` row order.\n */\n order?: GristTableRowsOrder\n /**\n * Enable safe parsing with issue reporting.\n * - `true`: use safe parsing with defaults.\n * - object: pass safe-parse options (`displayHelpersByRef`, `refMode`, ...).\n */\n safeParse?: boolean | Omit<SafeParseGristTableDataOptions, \"columns\" | \"order\">\n /** Optional callback fired with per-cell parse issues when `safeParse` is enabled. */\n onSafeParseIssues?: (issues: GristSafeTableCellIssue[]) => void\n /** Optional callback fired with enriched per-cell safe-parse metadata. */\n onSafeParseCellMeta?: (meta: GristSafeTableCellMeta[]) => void\n /** Optional callback fired with keyed per-cell metadata (`${rowId}:${colId}`). */\n onSafeParseCellMetaByKey?: (metaByKey: Record<string, GristSafeTableCellMeta>) => void\n}\n\nfunction inferDisplayHelpersByNameFromData(data: GristColumnarTable): Record<string, string> {\n const out: Record<string, string> = {}\n for (const colId of Object.keys(data)) {\n if (!colId.startsWith(\"gristHelper_\")) continue\n const baseColId = colId.slice(\"gristHelper_\".length)\n if (!baseColId) continue\n if (!Array.isArray(data[baseColId])) continue\n out[baseColId] = colId\n }\n return out\n}\n\nfunction enrichSafeParseColumnsFromData(\n data: GristColumnarTable,\n columns?: Record<string, GristReplicaColumn>\n): Record<string, GristReplicaColumn> {\n const out: Record<string, GristReplicaColumn> = { ...(columns ?? {}) }\n const keys = Object.keys(data)\n const positionByColId = new Map<string, number>(keys.map((colId, index) => [colId, index + 1]))\n\n // Ensure helper columns exist in safe-parse metadata even when caller schema\n // excluded them (common for UI schemas). Parent positions are synthesized from\n // fetchTable key order so helper/ref fallback matching still works.\n for (const colId of keys) {\n if (!colId.startsWith(\"gristHelper_\")) continue\n if (out[colId]) continue\n out[colId] = {\n type: \"Any\",\n label: colId,\n parentPos: positionByColId.get(colId),\n }\n }\n return out\n}\n\nfunction rowsFromSafeCellResults(\n parsedByColumn: Record<string, GristSafeParseCellResult[]>\n): GristRowRecord<Record<string, unknown>>[] {\n const colIds = Object.keys(parsedByColumn)\n const rowCount = colIds.reduce((max, colId) => {\n const len = parsedByColumn[colId]?.length ?? 0\n return Math.max(max, len)\n }, 0)\n const rows: GristRowRecord<Record<string, unknown>>[] = []\n for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {\n const row: Record<string, unknown> = {}\n for (const colId of colIds) {\n row[colId] = parsedByColumn[colId]?.[rowIndex] ?? null\n }\n rows.push(row as GristRowRecord<Record<string, unknown>>)\n }\n return rows\n}\n\nfunction safeCellRowId(row: Record<string, unknown>): number | null {\n const idCell = row.id\n if (!idCell || typeof idCell !== \"object\" || !(\"typedValue\" in idCell)) return null\n const typed = (idCell as { typedValue?: unknown }).typedValue\n return typeof typed === \"number\" && Number.isFinite(typed) ? typed : null\n}\n\nfunction orderSafeCellRowsByParsedRows(\n rows: GristRowRecord<Record<string, unknown>>[],\n orderedParsedRows: ReadonlyArray<Record<string, unknown>>\n): GristRowRecord<Record<string, unknown>>[] {\n const byId = new Map<number, GristRowRecord<Record<string, unknown>>[]>()\n for (const row of rows) {\n const id = safeCellRowId(row)\n if (id === null) continue\n const bucket = byId.get(id)\n if (bucket) bucket.push(row)\n else byId.set(id, [row])\n }\n const out: GristRowRecord<Record<string, unknown>>[] = []\n for (const parsedRow of orderedParsedRows) {\n const id = typeof parsedRow.id === \"number\" ? parsedRow.id : null\n if (id === null) continue\n const bucket = byId.get(id)\n if (!bucket || bucket.length === 0) continue\n const next = bucket.shift()\n if (next) out.push(next)\n }\n // Preserve any unexpected leftovers (defensive).\n for (const bucket of byId.values()) {\n out.push(...bucket)\n }\n return out\n}\n\n/**\n * Project safe-parse rows to UI display rows by taking each cell's `displayValue`.\n * Useful for grids that render human-friendly values while keeping safe-cell\n * objects available for logic and metadata.\n */\nexport function safeParseRowsToDisplayRows(\n rows: ReadonlyArray<Record<string, unknown>>\n): GristRowRecord<Record<string, unknown>>[] {\n return rows.map((row) => {\n const out: Record<string, unknown> = {}\n for (const [colId, cell] of Object.entries(row)) {\n if (cell && typeof cell === \"object\" && \"displayValue\" in cell) {\n out[colId] = (cell as { displayValue: unknown }).displayValue\n } else {\n out[colId] = cell\n }\n }\n return out as GristRowRecord<Record<string, unknown>>\n })\n}\n\n/** Row count from columnar `fetchTable` data (prefers `id` column length). */\nexport function computeRowCount(data: Record<string, unknown[]>): number {\n const idCol = data.id\n if (Array.isArray(idCol)) return idCol.length\n for (const key of Object.keys(data)) {\n const col = data[key]\n if (Array.isArray(col)) return col.length\n }\n return 0\n}\n\n/**\n * Decode columnar `fetchTable` into replica row objects (same convention as the playground grid).\n */\nexport function decodeColumnarToReplicaRows(\n data: GristColumnarTable,\n columnIds: string[],\n columns: Record<string, GristReplicaColumn>,\n maxRows?: number\n): GristRowRecord[] {\n const nFull = computeRowCount(data)\n const n = maxRows !== undefined ? Math.min(nFull, maxRows) : nFull\n const out: GristRowRecord[] = []\n for (let i = 0; i < n; i++) {\n const row: Record<string, unknown> = {}\n for (const k of columnIds) {\n const arr = data[k]\n const raw = Array.isArray(arr) ? arr[i] : undefined\n if (k === \"id\") {\n row[k] = raw\n } else {\n row[k] = decodeGristValue(raw, columns[k]) as unknown\n }\n }\n out.push(row as GristRowRecord)\n }\n return out\n}\n\n/** Turn `fetchTable` output into row objects (order follows the `id` column). */\nexport function tableDataToRows<T = Record<string, unknown>>(\n data: GristColumnarTable\n): GristRowRecord<T>[] {\n const idCol = data.id\n if (!Array.isArray(idCol) || idCol.length === 0) return []\n const keys = Object.keys(data)\n const n = idCol.length\n const rows: GristRowRecord<T>[] = []\n for (let i = 0; i < n; i++) {\n const row: Record<string, unknown> = {}\n for (const k of keys) {\n const col = data[k]\n row[k] = Array.isArray(col) ? col[i] : undefined\n }\n rows.push(row as GristRowRecord<T>)\n }\n return rows\n}\n\nfunction manualSortKey(row: Record<string, unknown>): number | null {\n const v = row.manualSort\n if (typeof v === \"number\" && !Number.isNaN(v)) return v\n if (typeof v === \"string\" && v.trim() !== \"\") {\n const n = Number(v)\n if (!Number.isNaN(n)) return n\n }\n return null\n}\n\n/**\n * Sort rows by Grist's display order: `manualSort` ascending when present on\n * any row, otherwise numeric `id` ascending.\n */\nexport function sortRowsByGristOrder<T = Record<string, unknown>>(\n rows: readonly GristRowRecord<T>[]\n): GristRowRecord<T>[] {\n const hasManual = rows.some((r) => manualSortKey(r as Record<string, unknown>) !== null)\n if (!hasManual) {\n return [...rows].sort((a, b) => {\n const ida = typeof a.id === \"number\" ? a.id : 0\n const idb = typeof b.id === \"number\" ? b.id : 0\n return ida - idb\n })\n }\n return [...rows].sort((a, b) => {\n const ma = manualSortKey(a as Record<string, unknown>)\n const mb = manualSortKey(b as Record<string, unknown>)\n if (ma !== null && mb !== null && ma !== mb) return ma - mb\n if (ma !== null && mb === null) return -1\n if (ma === null && mb !== null) return 1\n const ida = typeof a.id === \"number\" ? a.id : 0\n const idb = typeof b.id === \"number\" ? b.id : 0\n return ida - idb\n })\n}\n\n/** Row index where `id` matches `rowId`, or `null`. */\nexport function findRowIndexById(data: GristColumnarTable, rowId: number | string): number | null {\n const idCol = data.id\n if (!Array.isArray(idCol)) return null\n const idx = idCol.findIndex((v) => v === rowId || (typeof v === \"number\" && v === Number(rowId)))\n return idx === -1 ? null : idx\n}\n\n/** Single row at `index`, or `null` if out of range. */\nexport function rowAtIndex<T = Record<string, unknown>>(\n data: GristColumnarTable,\n index: number\n): GristRowRecord<T> | null {\n const idCol = data.id\n if (!Array.isArray(idCol) || index < 0 || index >= idCol.length) return null\n const keys = Object.keys(data)\n const row: Record<string, unknown> = {}\n for (const k of keys) {\n const col = data[k]\n row[k] = Array.isArray(col) ? col[index] : undefined\n }\n return row as GristRowRecord<T>\n}\n\n/**\n * Fetch a single row from any table by id. Convenience around\n * `docApi.fetchTable` + {@link findRowIndexById} + {@link rowAtIndex}.\n */\nexport async function fetchGristRow<T = Record<string, unknown>>(\n docApi: GristDocApi,\n tableId: string,\n rowId: number\n): Promise<GristRowRecord<T> | null> {\n const data = (await docApi.fetchTable(tableId)) as GristColumnarTable\n const index = findRowIndexById(data, rowId)\n if (index === null) return null\n return rowAtIndex<T>(data, index)\n}\n\n/**\n * Same row materialization as {@link fetchGristTableRows} after `fetchTable`\n * returns columnar data. Hosts that already have columnar data (e.g. the\n * emulator building from in-memory rows) can use this and skip `docApi.fetchTable`.\n */\nexport function materializeGristRowsFromColumnar<T extends Record<string, unknown> = Record<string, unknown>>(\n data: GristColumnarTable,\n options?: FetchGristTableRowsOptions\n): GristRowRecord<T>[] {\n const order = options?.order ?? \"grist\"\n if (options?.safeParse) {\n const safeOptions = options.safeParse === true ? {} : options.safeParse\n const columnsForSafeParse = enrichSafeParseColumnsFromData(data, options?.columns)\n const displayHelpersByRef = {\n ...inferDisplayHelpersByNameFromData(data),\n ...(safeOptions.displayHelpersByRef ?? {}),\n }\n const parsed = safeParseGristTableData<T>(data, {\n ...safeOptions,\n columns: columnsForSafeParse,\n displayHelpersByRef,\n order,\n })\n options.onSafeParseIssues?.(parsed.cellIssues)\n options.onSafeParseCellMeta?.(parsed.cellMeta)\n options.onSafeParseCellMetaByKey?.(parsed.cellMetaByKey)\n const rawSafeRows = rowsFromSafeCellResults(parsed.cellResultsByColumn)\n const orderedSafeRows =\n order === \"none\"\n ? rawSafeRows\n : orderSafeCellRowsByParsedRows(rawSafeRows, parsed.rows as ReadonlyArray<Record<string, unknown>>)\n return orderedSafeRows as GristRowRecord<T>[]\n }\n\n let rows: GristRowRecord<T>[]\n if (options?.columns) {\n const columnIds = Object.keys(data)\n rows = decodeColumnarToReplicaRows(data, columnIds, options.columns) as GristRowRecord<T>[]\n } else {\n rows = tableDataToRows<T>(data)\n }\n\n if (order === \"none\") return rows\n if (order === \"id\") {\n return [...rows].sort((a, b) => {\n const ida = typeof a.id === \"number\" ? a.id : 0\n const idb = typeof b.id === \"number\" ? b.id : 0\n return ida - idb\n })\n }\n return sortRowsByGristOrder(rows)\n}\n\n/**\n * Fetch a table and return rows as objects, optionally decoded from schema and sorted.\n */\nexport async function fetchGristTableRows<T extends Record<string, unknown> = Record<string, unknown>>(\n docApi: GristDocApi,\n tableId: string,\n options: FetchGristTableRowsOptions & { safeParse: true | Omit<SafeParseGristTableDataOptions, \"columns\" | \"order\"> }\n): Promise<GristRowRecord<{ [K in keyof T]: GristSafeParseCellResult<T[K]> | null }>[]>\nexport async function fetchGristTableRows<T extends Record<string, unknown> = Record<string, unknown>>(\n docApi: GristDocApi,\n tableId: string,\n options?: FetchGristTableRowsOptions\n): Promise<GristRowRecord<T>[]>\nexport async function fetchGristTableRows<T extends Record<string, unknown> = Record<string, unknown>>(\n docApi: GristDocApi,\n tableId: string,\n options?: FetchGristTableRowsOptions\n): Promise<GristRowRecord<T>[]> {\n const data = (await docApi.fetchTable(tableId)) as GristColumnarTable\n return materializeGristRowsFromColumnar<T>(data, options)\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-N3X46JW2.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// src/sdk/internal/handshake/access.ts
|
|
2
|
-
var ACCESS_RANK = {
|
|
3
|
-
none: 0,
|
|
4
|
-
"read table": 1,
|
|
5
|
-
full: 2
|
|
6
|
-
};
|
|
7
|
-
var GRIST_ACCESS_LEVELS = ["none", "read table", "full"];
|
|
8
|
-
function parseGristRequiredAccess(raw) {
|
|
9
|
-
if (!raw) return null;
|
|
10
|
-
return GRIST_ACCESS_LEVELS.includes(raw) ? raw : null;
|
|
11
|
-
}
|
|
12
|
-
function isAccessSufficient(granted, required) {
|
|
13
|
-
return ACCESS_RANK[granted] >= ACCESS_RANK[required];
|
|
14
|
-
}
|
|
15
|
-
function gristAccessLevelLabel(level) {
|
|
16
|
-
switch (level) {
|
|
17
|
-
case "none":
|
|
18
|
-
return "No document access";
|
|
19
|
-
case "read table":
|
|
20
|
-
return "Read selected table";
|
|
21
|
-
case "full":
|
|
22
|
-
return "Full document access";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function formatInsufficientAccessMessage(required, _granted) {
|
|
26
|
-
const need = gristAccessLevelLabel(required);
|
|
27
|
-
return `Set Access level to at least "${need}" in the Custom widget settings.`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
ACCESS_RANK,
|
|
32
|
-
parseGristRequiredAccess,
|
|
33
|
-
isAccessSufficient,
|
|
34
|
-
gristAccessLevelLabel,
|
|
35
|
-
formatInsufficientAccessMessage
|
|
36
|
-
};
|
|
37
|
-
//# sourceMappingURL=chunk-N3X46JW2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sdk/internal/handshake/access.ts"],"sourcesContent":["/**\n * Access-level helpers used by the handshake state machine.\n *\n * Kept in a tiny dedicated file because the same ranking is needed by the\n * reducer (`widened` flag, future re-negotiation logic) and potentially by\n * effects (decide whether to re-issue `grist.ready`).\n */\n\nimport type { GristRequiredAccess } from \"../../types\"\n\n/** Strictly-ordered ranking. Higher rank = stronger access. */\nexport const ACCESS_RANK: Record<GristRequiredAccess, number> = {\n none: 0,\n \"read table\": 1,\n full: 2,\n}\n\nconst GRIST_ACCESS_LEVELS: readonly GristRequiredAccess[] = [\"none\", \"read table\", \"full\"]\n\n/** Parse `interaction.access_level` / `accessLevel` from Grist `onOptions`. */\nexport function parseGristRequiredAccess(\n raw: string | undefined\n): GristRequiredAccess | null {\n if (!raw) return null\n return (GRIST_ACCESS_LEVELS as readonly string[]).includes(raw)\n ? (raw as GristRequiredAccess)\n : null\n}\n\n/** True when the host-granted level meets what the widget declared in `grist.ready`. */\nexport function isAccessSufficient(\n granted: GristRequiredAccess,\n required: GristRequiredAccess\n): boolean {\n return ACCESS_RANK[granted] >= ACCESS_RANK[required]\n}\n\n/** Creator-panel label for a Grist access level (for user-facing copy). */\nexport function gristAccessLevelLabel(level: GristRequiredAccess): string {\n switch (level) {\n case \"none\":\n return \"No document access\"\n case \"read table\":\n return \"Read selected table\"\n case \"full\":\n return \"Full document access\"\n }\n}\n\nexport function formatInsufficientAccessMessage(\n required: GristRequiredAccess,\n _granted: GristRequiredAccess\n): string {\n const need = gristAccessLevelLabel(required)\n return `Set Access level to at least \"${need}\" in the Custom widget settings.`\n}\n\nexport function isAccessWidening(\n previous: GristRequiredAccess | null,\n next: GristRequiredAccess\n): boolean {\n if (previous === null) return true\n return ACCESS_RANK[next] > ACCESS_RANK[previous]\n}\n"],"mappings":";AAWO,IAAM,cAAmD;AAAA,EAC9D,MAAM;AAAA,EACN,cAAc;AAAA,EACd,MAAM;AACR;AAEA,IAAM,sBAAsD,CAAC,QAAQ,cAAc,MAAM;AAGlF,SAAS,yBACd,KAC4B;AAC5B,MAAI,CAAC,IAAK,QAAO;AACjB,SAAQ,oBAA0C,SAAS,GAAG,IACzD,MACD;AACN;AAGO,SAAS,mBACd,SACA,UACS;AACT,SAAO,YAAY,OAAO,KAAK,YAAY,QAAQ;AACrD;AAGO,SAAS,sBAAsB,OAAoC;AACxE,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAEO,SAAS,gCACd,UACA,UACQ;AACR,QAAM,OAAO,sBAAsB,QAAQ;AAC3C,SAAO,iCAAiC,IAAI;AAC9C;","names":[]}
|