grist-widget-sdk 0.3.25 → 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 +20 -0
- package/dist/advanced.d.cts +3 -2
- package/dist/advanced.d.ts +3 -2
- package/dist/advanced.js +10 -13
- package/dist/advanced.js.map +1 -1
- package/dist/app.cjs +5176 -0
- package/dist/app.cjs.map +1 -0
- package/dist/app.d.cts +147 -0
- package/dist/app.d.ts +147 -0
- package/dist/app.js +23 -0
- package/dist/app.js.map +1 -0
- package/dist/chunk-6Y2IONGJ.js +519 -0
- package/dist/chunk-6Y2IONGJ.js.map +1 -0
- package/dist/chunk-B4AKVSWV.js +2362 -0
- package/dist/chunk-B4AKVSWV.js.map +1 -0
- 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/{create-grist-emulator-_3_JK7Rr.d.ts → create-grist-emulator-BCqp1BF9.d.ts} +2 -2
- package/dist/{create-grist-emulator-K-iUKUCZ.d.cts → create-grist-emulator-DBM8OSIR.d.cts} +2 -2
- 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 +56 -5
- package/dist/emulator/testing.d.ts +56 -5
- package/dist/emulator/testing.js +142 -4
- package/dist/emulator/testing.js.map +1 -1
- package/dist/emulator/transports.d.cts +3 -3
- package/dist/emulator/transports.d.ts +3 -3
- package/dist/emulator.d.cts +5 -5
- package/dist/emulator.d.ts +5 -5
- package/dist/index.d.cts +9 -500
- package/dist/index.d.ts +9 -500
- package/dist/index.js +57 -2294
- package/dist/index.js.map +1 -1
- package/dist/{inline-A8yaddmp.d.cts → inline-C7DkvuET.d.cts} +1 -1
- package/dist/{inline-B7QQeWul.d.ts → inline-DpcT8tV9.d.ts} +1 -1
- package/dist/{templates-CB_VwjEU.d.cts → templates-8k2LKoO-.d.cts} +1 -1
- package/dist/{templates-pRIXc2RR.d.ts → templates-DIwi_mvI.d.ts} +1 -1
- package/dist/{types-YcRfo7oy.d.cts → types-D3JxUCfL.d.cts} +1 -1
- package/dist/{types-YcRfo7oy.d.ts → types-D3JxUCfL.d.ts} +1 -1
- package/dist/{use-grist-widget-options-from-context-hl-NwOwP.d.ts → use-grist-reads-DUMvJgV7.d.ts} +3 -44
- package/dist/{use-grist-widget-options-from-context-Bbd5-f39.d.cts → use-grist-reads-DpjVuI0H.d.cts} +3 -44
- package/dist/use-grist-types-DdOLEJXN.d.cts +499 -0
- package/dist/use-grist-types-kwxoFeub.d.ts +499 -0
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.cts +43 -0
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.ts +43 -0
- package/package.json +11 -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
|
@@ -3,7 +3,64 @@ import {
|
|
|
3
3
|
formatInsufficientAccessMessage,
|
|
4
4
|
isAccessSufficient,
|
|
5
5
|
parseGristRequiredAccess
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OPTOYD2Y.js";
|
|
7
|
+
|
|
8
|
+
// src/sdk/helpers/grist-fetch-with-auth.ts
|
|
9
|
+
var TOKEN_TTL_MS = 45e3;
|
|
10
|
+
var cache = /* @__PURE__ */ new Map();
|
|
11
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
12
|
+
function cacheKey(readOnly) {
|
|
13
|
+
return readOnly ? "ro" : "rw";
|
|
14
|
+
}
|
|
15
|
+
async function getGristAccessToken(docApi, options) {
|
|
16
|
+
if (!docApi.getAccessToken) {
|
|
17
|
+
throw new Error("getAccessToken is not available on docApi (need higher access level)");
|
|
18
|
+
}
|
|
19
|
+
const readOnly = options?.readOnly ?? false;
|
|
20
|
+
const key = cacheKey(readOnly);
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const hit = cache.get(key);
|
|
23
|
+
if (hit && now - hit.fetchedAt < TOKEN_TTL_MS) {
|
|
24
|
+
return hit.token;
|
|
25
|
+
}
|
|
26
|
+
const pending = inFlight.get(key);
|
|
27
|
+
if (pending) return pending;
|
|
28
|
+
const next = docApi.getAccessToken({ readOnly }).then((token) => {
|
|
29
|
+
cache.set(key, { token, fetchedAt: Date.now() });
|
|
30
|
+
inFlight.delete(key);
|
|
31
|
+
return token;
|
|
32
|
+
});
|
|
33
|
+
inFlight.set(key, next);
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
function clearGristAccessTokenCache() {
|
|
37
|
+
cache.clear();
|
|
38
|
+
inFlight.clear();
|
|
39
|
+
}
|
|
40
|
+
async function fetchWithGristAuth(docApi, path, init) {
|
|
41
|
+
const readOnly = init?.readOnly ?? false;
|
|
42
|
+
let token;
|
|
43
|
+
try {
|
|
44
|
+
token = await getGristAccessToken(docApi, { readOnly });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
clearGristAccessTokenCache();
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
const isAbsolute = /^https?:\/\//i.test(path);
|
|
50
|
+
const cleanPath = isAbsolute ? path : path.startsWith("/") ? path : `/${path}`;
|
|
51
|
+
const url = isAbsolute ? new URL(path) : new URL(`${token.baseUrl.replace(/\/$/, "")}${cleanPath}`);
|
|
52
|
+
if (!url.searchParams.has("auth")) url.searchParams.set("auth", token.token);
|
|
53
|
+
const headers = new Headers(init?.headers ?? {});
|
|
54
|
+
headers.delete("Authorization");
|
|
55
|
+
const response = await fetch(url.toString(), { ...init, headers });
|
|
56
|
+
if (response.status === 401) {
|
|
57
|
+
clearGristAccessTokenCache();
|
|
58
|
+
const retryToken = await getGristAccessToken(docApi, { readOnly });
|
|
59
|
+
url.searchParams.set("auth", retryToken.token);
|
|
60
|
+
return fetch(url.toString(), { ...init, headers });
|
|
61
|
+
}
|
|
62
|
+
return response;
|
|
63
|
+
}
|
|
7
64
|
|
|
8
65
|
// src/sdk/helpers/grist-mappings.ts
|
|
9
66
|
function asColumnSpec(entry) {
|
|
@@ -42,6 +99,9 @@ var pendingColumnMappingStatus = {
|
|
|
42
99
|
emptyMultiples: []
|
|
43
100
|
};
|
|
44
101
|
|
|
102
|
+
// src/sdk/internal/hooks/use-grist-core.ts
|
|
103
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
104
|
+
|
|
45
105
|
// src/sdk/internal/handshake/types.ts
|
|
46
106
|
var ZERO_GENERATION = 0;
|
|
47
107
|
function nextGeneration(current) {
|
|
@@ -70,142 +130,6 @@ function initialGristSnapshot() {
|
|
|
70
130
|
};
|
|
71
131
|
}
|
|
72
132
|
|
|
73
|
-
// src/sdk/internal/handshake/derive.ts
|
|
74
|
-
function deriveStatus(snapshot) {
|
|
75
|
-
const { lifecycle, authz, link } = snapshot;
|
|
76
|
-
if (lifecycle.phase === "idle" || lifecycle.phase === "detecting") return "booting";
|
|
77
|
-
if (lifecycle.phase === "negotiating") return "booting";
|
|
78
|
-
if (lifecycle.phase === "terminated") {
|
|
79
|
-
switch (lifecycle.reason.kind) {
|
|
80
|
-
case "script_timeout":
|
|
81
|
-
case "not_embedded":
|
|
82
|
-
return "unavailable";
|
|
83
|
-
case "host_rejected":
|
|
84
|
-
case "ready_failed":
|
|
85
|
-
case "ready_timeout":
|
|
86
|
-
case "manual_terminate":
|
|
87
|
-
return "error";
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (authz.state === "denied") return "error";
|
|
91
|
-
if (link.state === "lost") return "error";
|
|
92
|
-
return "ready";
|
|
93
|
-
}
|
|
94
|
-
function deriveErrorMessage(snapshot) {
|
|
95
|
-
if (snapshot.lifecycle.phase === "terminated") {
|
|
96
|
-
switch (snapshot.lifecycle.reason.kind) {
|
|
97
|
-
case "ready_failed":
|
|
98
|
-
return snapshot.lifecycle.reason.message;
|
|
99
|
-
case "ready_timeout":
|
|
100
|
-
return `Grist handshake timed out after ${snapshot.lifecycle.reason.afterMs}ms`;
|
|
101
|
-
case "host_rejected":
|
|
102
|
-
return snapshot.lifecycle.reason.message;
|
|
103
|
-
case "script_timeout":
|
|
104
|
-
return `Grist plugin API never loaded (${snapshot.lifecycle.reason.attempts} attempts)`;
|
|
105
|
-
case "not_embedded":
|
|
106
|
-
return "This page is not embedded in a Grist iframe";
|
|
107
|
-
case "manual_terminate":
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (snapshot.authz.state === "denied") {
|
|
112
|
-
return snapshot.authz.message ?? `Grist denied requested access (${snapshot.authz.declared})`;
|
|
113
|
-
}
|
|
114
|
-
if (snapshot.lifecycle.phase === "online" && snapshot.link.state === "lost") {
|
|
115
|
-
return `Grist link lost (${snapshot.link.missedPings} consecutive failures)`;
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
function deriveCapabilities(snapshot) {
|
|
120
|
-
const { lifecycle, authz, link, config, sync } = snapshot;
|
|
121
|
-
const online = lifecycle.phase === "online";
|
|
122
|
-
const granted = authz.state === "granted";
|
|
123
|
-
const grantedReadOrMore = granted && ACCESS_RANK[authz.level] >= ACCESS_RANK["read table"];
|
|
124
|
-
const grantedFull = granted && authz.level === "full";
|
|
125
|
-
const linkUsable = link.state !== "lost";
|
|
126
|
-
const canRead = online && linkUsable && grantedReadOrMore;
|
|
127
|
-
const canFetchTable = canRead && link.state === "connected";
|
|
128
|
-
const mappingsUsable = hasUsableMappings(config.mappings);
|
|
129
|
-
const missing = mappingsMissing(config.mappings);
|
|
130
|
-
const emptyMultiples = mappingsEmptyMultiples(config.mappings);
|
|
131
|
-
const canRender = canRead && mappingsUsable;
|
|
132
|
-
const canWriteRecords = canRender && grantedFull;
|
|
133
|
-
const canWriteSchema = canWriteRecords && sync.currentTable.state === "known";
|
|
134
|
-
const hasFreshSelection = canRead && sync.record.state === "hot" && config.mappings.state !== "invalidating";
|
|
135
|
-
return {
|
|
136
|
-
canRead,
|
|
137
|
-
canRender,
|
|
138
|
-
canWriteRecords,
|
|
139
|
-
canWriteSchema,
|
|
140
|
-
canFetchTable,
|
|
141
|
-
hasFreshSelection,
|
|
142
|
-
hasUsableMappings: mappingsUsable,
|
|
143
|
-
missingMappings: missing,
|
|
144
|
-
emptyMultipleMappings: emptyMultiples
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
function hasUsableMappings(state) {
|
|
148
|
-
if (state.state === "not_declared") return true;
|
|
149
|
-
if (state.state === "complete") return true;
|
|
150
|
-
if (state.state === "invalidating") {
|
|
151
|
-
return state.previous.state === "complete";
|
|
152
|
-
}
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
function mappingsMissing(state) {
|
|
156
|
-
if (state.state === "incomplete") return state.missing;
|
|
157
|
-
if (state.state === "invalidating" && state.previous.state === "incomplete") {
|
|
158
|
-
return state.previous.missing;
|
|
159
|
-
}
|
|
160
|
-
return EMPTY;
|
|
161
|
-
}
|
|
162
|
-
function mappingsEmptyMultiples(state) {
|
|
163
|
-
if (state.state === "incomplete") return state.emptyMultiples;
|
|
164
|
-
if (state.state === "invalidating" && state.previous.state === "incomplete") {
|
|
165
|
-
return state.previous.emptyMultiples;
|
|
166
|
-
}
|
|
167
|
-
return EMPTY;
|
|
168
|
-
}
|
|
169
|
-
var EMPTY = Object.freeze([]);
|
|
170
|
-
function extractResolvedMappings(state) {
|
|
171
|
-
switch (state.state) {
|
|
172
|
-
case "complete":
|
|
173
|
-
case "incomplete":
|
|
174
|
-
return state.mappings;
|
|
175
|
-
case "invalidating":
|
|
176
|
-
if (state.previous.state === "complete" || state.previous.state === "incomplete") {
|
|
177
|
-
return state.previous.mappings;
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
180
|
-
default:
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function deriveColumnMappingStatus(mappingState, columns) {
|
|
185
|
-
if (!columns?.length) {
|
|
186
|
-
return { ok: true, pending: false, missing: [], emptyMultiples: [] };
|
|
187
|
-
}
|
|
188
|
-
switch (mappingState.state) {
|
|
189
|
-
case "not_declared":
|
|
190
|
-
return { ok: true, pending: false, missing: [], emptyMultiples: [] };
|
|
191
|
-
case "pending":
|
|
192
|
-
return pendingColumnMappingStatus;
|
|
193
|
-
case "unreported":
|
|
194
|
-
return pendingColumnMappingStatus;
|
|
195
|
-
case "complete":
|
|
196
|
-
return { ok: true, pending: false, missing: [], emptyMultiples: [] };
|
|
197
|
-
case "incomplete":
|
|
198
|
-
return {
|
|
199
|
-
ok: false,
|
|
200
|
-
pending: false,
|
|
201
|
-
missing: [...mappingState.missing],
|
|
202
|
-
emptyMultiples: [...mappingState.emptyMultiples]
|
|
203
|
-
};
|
|
204
|
-
case "invalidating":
|
|
205
|
-
return deriveColumnMappingStatus(mappingState.previous, columns);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
133
|
// src/sdk/internal/handshake/actions.ts
|
|
210
134
|
function hasGenerationStamp(action) {
|
|
211
135
|
return "generation" in action && typeof action.generation === "number";
|
|
@@ -707,197 +631,212 @@ function updateSync(snapshot, sync) {
|
|
|
707
631
|
return { ...snapshot, sync };
|
|
708
632
|
}
|
|
709
633
|
|
|
710
|
-
// src/sdk/
|
|
711
|
-
function
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
return
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
634
|
+
// src/sdk/internal/handshake/derive.ts
|
|
635
|
+
function deriveStatus(snapshot) {
|
|
636
|
+
const { lifecycle, authz, link } = snapshot;
|
|
637
|
+
if (lifecycle.phase === "idle" || lifecycle.phase === "detecting") return "booting";
|
|
638
|
+
if (lifecycle.phase === "negotiating") return "booting";
|
|
639
|
+
if (lifecycle.phase === "terminated") {
|
|
640
|
+
switch (lifecycle.reason.kind) {
|
|
641
|
+
case "script_timeout":
|
|
642
|
+
case "not_embedded":
|
|
643
|
+
return "unavailable";
|
|
644
|
+
case "host_rejected":
|
|
645
|
+
case "ready_failed":
|
|
646
|
+
case "ready_timeout":
|
|
647
|
+
case "manual_terminate":
|
|
648
|
+
return "error";
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (authz.state === "denied") return "error";
|
|
652
|
+
if (link.state === "lost") return "error";
|
|
653
|
+
return "ready";
|
|
718
654
|
}
|
|
719
|
-
function
|
|
720
|
-
if (
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
655
|
+
function deriveErrorMessage(snapshot) {
|
|
656
|
+
if (snapshot.lifecycle.phase === "terminated") {
|
|
657
|
+
switch (snapshot.lifecycle.reason.kind) {
|
|
658
|
+
case "ready_failed":
|
|
659
|
+
return snapshot.lifecycle.reason.message;
|
|
660
|
+
case "ready_timeout":
|
|
661
|
+
return `Grist handshake timed out after ${snapshot.lifecycle.reason.afterMs}ms`;
|
|
662
|
+
case "host_rejected":
|
|
663
|
+
return snapshot.lifecycle.reason.message;
|
|
664
|
+
case "script_timeout":
|
|
665
|
+
return `Grist plugin API never loaded (${snapshot.lifecycle.reason.attempts} attempts)`;
|
|
666
|
+
case "not_embedded":
|
|
667
|
+
return "This page is not embedded in a Grist iframe";
|
|
668
|
+
case "manual_terminate":
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (snapshot.authz.state === "denied") {
|
|
673
|
+
return snapshot.authz.message ?? `Grist denied requested access (${snapshot.authz.declared})`;
|
|
674
|
+
}
|
|
675
|
+
if (snapshot.lifecycle.phase === "online" && snapshot.link.state === "lost") {
|
|
676
|
+
return `Grist link lost (${snapshot.link.missedPings} consecutive failures)`;
|
|
677
|
+
}
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
function deriveCapabilities(snapshot) {
|
|
681
|
+
const { lifecycle, authz, link, config, sync } = snapshot;
|
|
682
|
+
const online = lifecycle.phase === "online";
|
|
683
|
+
const granted = authz.state === "granted";
|
|
684
|
+
const grantedReadOrMore = granted && ACCESS_RANK[authz.level] >= ACCESS_RANK["read table"];
|
|
685
|
+
const grantedFull = granted && authz.level === "full";
|
|
686
|
+
const linkUsable = link.state !== "lost";
|
|
687
|
+
const canRead = online && linkUsable && grantedReadOrMore;
|
|
688
|
+
const canFetchTable = canRead && link.state === "connected";
|
|
689
|
+
const mappingsUsable = hasUsableMappings(config.mappings);
|
|
690
|
+
const missing = mappingsMissing(config.mappings);
|
|
691
|
+
const emptyMultiples = mappingsEmptyMultiples(config.mappings);
|
|
692
|
+
const canRender = canRead && mappingsUsable;
|
|
693
|
+
const canWriteRecords = canRender && grantedFull;
|
|
694
|
+
const canWriteSchema = canWriteRecords && sync.currentTable.state === "known";
|
|
695
|
+
const hasFreshSelection = canRead && sync.record.state === "hot" && config.mappings.state !== "invalidating";
|
|
724
696
|
return {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
697
|
+
canRead,
|
|
698
|
+
canRender,
|
|
699
|
+
canWriteRecords,
|
|
700
|
+
canWriteSchema,
|
|
701
|
+
canFetchTable,
|
|
702
|
+
hasFreshSelection,
|
|
703
|
+
hasUsableMappings: mappingsUsable,
|
|
704
|
+
missingMappings: missing,
|
|
705
|
+
emptyMultipleMappings: emptyMultiples
|
|
728
706
|
};
|
|
729
707
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
full: 2
|
|
736
|
-
};
|
|
737
|
-
var inFlight = null;
|
|
738
|
-
var lastAccess = null;
|
|
739
|
-
var lastColumnNames = null;
|
|
740
|
-
function columnNamesOf(columns) {
|
|
741
|
-
return new Set((columns ?? []).map((c) => typeof c === "string" ? c : c.name));
|
|
742
|
-
}
|
|
743
|
-
function shouldReinit(next, nextColumns) {
|
|
744
|
-
if (!lastAccess) return true;
|
|
745
|
-
if (ACCESS_RANK2[next] > ACCESS_RANK2[lastAccess]) return true;
|
|
746
|
-
for (const name of nextColumns) {
|
|
747
|
-
if (!lastColumnNames?.has(name)) return true;
|
|
708
|
+
function hasUsableMappings(state) {
|
|
709
|
+
if (state.state === "not_declared") return true;
|
|
710
|
+
if (state.state === "complete") return true;
|
|
711
|
+
if (state.state === "invalidating") {
|
|
712
|
+
return state.previous.state === "complete";
|
|
748
713
|
}
|
|
749
714
|
return false;
|
|
750
715
|
}
|
|
751
|
-
function
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
lastAccess = access;
|
|
756
|
-
lastColumnNames = columns;
|
|
757
|
-
const next = Promise.resolve().then(() => {
|
|
758
|
-
const result = grist.ready(payload);
|
|
759
|
-
return result instanceof Promise ? result : Promise.resolve();
|
|
760
|
-
});
|
|
761
|
-
inFlight = next.catch((err) => {
|
|
762
|
-
inFlight = null;
|
|
763
|
-
lastAccess = null;
|
|
764
|
-
lastColumnNames = null;
|
|
765
|
-
throw err;
|
|
766
|
-
});
|
|
767
|
-
return inFlight;
|
|
768
|
-
}
|
|
769
|
-
function resetGristReadySingleton() {
|
|
770
|
-
inFlight = null;
|
|
771
|
-
lastAccess = null;
|
|
772
|
-
lastColumnNames = null;
|
|
773
|
-
}
|
|
774
|
-
var resetGristReadyCacheForTests = resetGristReadySingleton;
|
|
775
|
-
|
|
776
|
-
// src/sdk/internal/subscriptions.ts
|
|
777
|
-
var GristEvent = class {
|
|
778
|
-
constructor(bindOnce) {
|
|
779
|
-
this.bindOnce = bindOnce;
|
|
780
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
781
|
-
this.hasValue = false;
|
|
782
|
-
this.bind = null;
|
|
783
|
-
}
|
|
784
|
-
subscribe(listener) {
|
|
785
|
-
this.listeners.add(listener);
|
|
786
|
-
if (!this.bind) {
|
|
787
|
-
this.bind = () => {
|
|
788
|
-
this.bind = () => {
|
|
789
|
-
};
|
|
790
|
-
try {
|
|
791
|
-
this.bindOnce((value) => {
|
|
792
|
-
this.hasValue = true;
|
|
793
|
-
this.last = value;
|
|
794
|
-
for (const l of this.listeners) l(value);
|
|
795
|
-
});
|
|
796
|
-
} catch (err) {
|
|
797
|
-
if (typeof console !== "undefined") {
|
|
798
|
-
console.error("[lib/grist] failed to subscribe to Grist event:", err);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
this.bind();
|
|
803
|
-
}
|
|
804
|
-
if (this.hasValue && this.last !== void 0) {
|
|
805
|
-
listener(this.last);
|
|
806
|
-
}
|
|
807
|
-
return () => {
|
|
808
|
-
this.listeners.delete(listener);
|
|
809
|
-
};
|
|
716
|
+
function mappingsMissing(state) {
|
|
717
|
+
if (state.state === "incomplete") return state.missing;
|
|
718
|
+
if (state.state === "invalidating" && state.previous.state === "incomplete") {
|
|
719
|
+
return state.previous.missing;
|
|
810
720
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
721
|
+
return EMPTY;
|
|
722
|
+
}
|
|
723
|
+
function mappingsEmptyMultiples(state) {
|
|
724
|
+
if (state.state === "incomplete") return state.emptyMultiples;
|
|
725
|
+
if (state.state === "invalidating" && state.previous.state === "incomplete") {
|
|
726
|
+
return state.previous.emptyMultiples;
|
|
814
727
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
728
|
+
return EMPTY;
|
|
729
|
+
}
|
|
730
|
+
var EMPTY = Object.freeze([]);
|
|
731
|
+
function extractResolvedMappings(state) {
|
|
732
|
+
switch (state.state) {
|
|
733
|
+
case "complete":
|
|
734
|
+
case "incomplete":
|
|
735
|
+
return state.mappings;
|
|
736
|
+
case "invalidating":
|
|
737
|
+
if (state.previous.state === "complete" || state.previous.state === "incomplete") {
|
|
738
|
+
return state.previous.mappings;
|
|
739
|
+
}
|
|
740
|
+
return null;
|
|
741
|
+
default:
|
|
742
|
+
return null;
|
|
818
743
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
deliverForTests(value) {
|
|
824
|
-
this.hasValue = true;
|
|
825
|
-
this.last = value;
|
|
826
|
-
for (const l of this.listeners) l(value);
|
|
744
|
+
}
|
|
745
|
+
function deriveColumnMappingStatus(mappingState, columns) {
|
|
746
|
+
if (!columns?.length) {
|
|
747
|
+
return { ok: true, pending: false, missing: [], emptyMultiples: [] };
|
|
827
748
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
749
|
+
switch (mappingState.state) {
|
|
750
|
+
case "not_declared":
|
|
751
|
+
return { ok: true, pending: false, missing: [], emptyMultiples: [] };
|
|
752
|
+
case "pending":
|
|
753
|
+
return pendingColumnMappingStatus;
|
|
754
|
+
case "unreported":
|
|
755
|
+
return pendingColumnMappingStatus;
|
|
756
|
+
case "complete":
|
|
757
|
+
return { ok: true, pending: false, missing: [], emptyMultiples: [] };
|
|
758
|
+
case "incomplete":
|
|
759
|
+
return {
|
|
760
|
+
ok: false,
|
|
761
|
+
pending: false,
|
|
762
|
+
missing: [...mappingState.missing],
|
|
763
|
+
emptyMultiples: [...mappingState.emptyMultiples]
|
|
764
|
+
};
|
|
765
|
+
case "invalidating":
|
|
766
|
+
return deriveColumnMappingStatus(mappingState.previous, columns);
|
|
838
767
|
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/sdk/internal/handshake-manager-context.ts
|
|
771
|
+
import { createContext, useContext, useEffect, useRef } from "react";
|
|
772
|
+
|
|
773
|
+
// src/sdk/internal/ready-singleton.ts
|
|
774
|
+
var ACCESS_RANK2 = {
|
|
775
|
+
none: 0,
|
|
776
|
+
"read table": 1,
|
|
777
|
+
full: 2
|
|
839
778
|
};
|
|
840
|
-
var
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
)
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
deliver(record, recordEvent.peek()?.mappings ?? null);
|
|
864
|
-
} catch (err) {
|
|
865
|
-
if (typeof console !== "undefined") {
|
|
866
|
-
console.error("[grist-widget-sdk] fetchSelectedRecord on cursor message failed:", err);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
});
|
|
871
|
-
var recordsEvent = new GristEvent((emit) => {
|
|
872
|
-
grist.onRecords(
|
|
873
|
-
(records, mappings) => {
|
|
874
|
-
emit({ records, mappings: mappings ?? null });
|
|
875
|
-
},
|
|
876
|
-
{ keepEncoded: false, format: "rows" }
|
|
877
|
-
);
|
|
878
|
-
});
|
|
879
|
-
var optionsEvent = new GristEvent((emit) => {
|
|
880
|
-
grist.onOptions((options, interaction) => {
|
|
881
|
-
const normalized = normalizeWidgetInteraction(
|
|
882
|
-
interaction ?? null
|
|
883
|
-
);
|
|
884
|
-
emit({
|
|
885
|
-
options: options ? { ...options } : null,
|
|
886
|
-
interaction: normalized
|
|
887
|
-
});
|
|
779
|
+
var inFlight2 = null;
|
|
780
|
+
var lastAccess = null;
|
|
781
|
+
var lastColumnNames = null;
|
|
782
|
+
function columnNamesOf(columns) {
|
|
783
|
+
return new Set((columns ?? []).map((c) => typeof c === "string" ? c : c.name));
|
|
784
|
+
}
|
|
785
|
+
function shouldReinit(next, nextColumns) {
|
|
786
|
+
if (!lastAccess) return true;
|
|
787
|
+
if (ACCESS_RANK2[next] > ACCESS_RANK2[lastAccess]) return true;
|
|
788
|
+
for (const name of nextColumns) {
|
|
789
|
+
if (!lastColumnNames?.has(name)) return true;
|
|
790
|
+
}
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
function ensureGristReady(payload) {
|
|
794
|
+
const access = payload.requiredAccess ?? "read table";
|
|
795
|
+
const columns = columnNamesOf(payload.columns);
|
|
796
|
+
if (inFlight2 && !shouldReinit(access, columns)) return inFlight2;
|
|
797
|
+
lastAccess = access;
|
|
798
|
+
lastColumnNames = columns;
|
|
799
|
+
const next = Promise.resolve().then(() => {
|
|
800
|
+
const result = grist.ready(payload);
|
|
801
|
+
return result instanceof Promise ? result : Promise.resolve();
|
|
888
802
|
});
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
803
|
+
inFlight2 = next.catch((err) => {
|
|
804
|
+
inFlight2 = null;
|
|
805
|
+
lastAccess = null;
|
|
806
|
+
lastColumnNames = null;
|
|
807
|
+
throw err;
|
|
894
808
|
});
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
809
|
+
return inFlight2;
|
|
810
|
+
}
|
|
811
|
+
function resetGristReadySingleton() {
|
|
812
|
+
inFlight2 = null;
|
|
813
|
+
lastAccess = null;
|
|
814
|
+
lastColumnNames = null;
|
|
815
|
+
}
|
|
816
|
+
var resetGristReadyCacheForTests = resetGristReadySingleton;
|
|
817
|
+
|
|
818
|
+
// src/sdk/internal/build-manager-options.ts
|
|
819
|
+
function buildManagerOptionsFromGristOptions(options) {
|
|
820
|
+
const opts = {
|
|
821
|
+
requiredAccess: options?.requiredAccess ?? "read table",
|
|
822
|
+
columns: options?.columns ?? [],
|
|
823
|
+
allowSelectBy: options?.allowSelectBy,
|
|
824
|
+
hasCustomOptions: options?.hasCustomOptions,
|
|
825
|
+
onEditOptions: options?.onEditOptions,
|
|
826
|
+
negotiate: {
|
|
827
|
+
readyImpl: (payload) => ensureGristReady(payload)
|
|
828
|
+
},
|
|
829
|
+
onBeforeReload: () => resetGristReadySingleton()
|
|
830
|
+
};
|
|
831
|
+
if (options?.availabilityMaxAttempts !== void 0 || options?.availabilityIntervalMs !== void 0) {
|
|
832
|
+
const interval = options?.availabilityIntervalMs ?? 1e3;
|
|
833
|
+
const max = options?.availabilityMaxAttempts ?? 10;
|
|
834
|
+
opts.detect = {
|
|
835
|
+
pollSequenceMs: Array.from({ length: max }, () => interval),
|
|
836
|
+
budgetMs: interval * max
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return opts;
|
|
901
840
|
}
|
|
902
841
|
|
|
903
842
|
// src/sdk/internal/handshake/effects/detect.ts
|
|
@@ -1049,6 +988,154 @@ function startNegotiateEffect(env, dispatch, signal, payload, options = {}) {
|
|
|
1049
988
|
}) };
|
|
1050
989
|
}
|
|
1051
990
|
|
|
991
|
+
// src/sdk/helpers/grist-widget-interaction.ts
|
|
992
|
+
function parseGristWidgetLinkingInfo(raw) {
|
|
993
|
+
if (!raw || typeof raw !== "object" || !("asTarget" in raw)) return void 0;
|
|
994
|
+
const linking = raw;
|
|
995
|
+
return {
|
|
996
|
+
asTarget: linking.asTarget == null ? null : String(linking.asTarget),
|
|
997
|
+
asSource: Boolean(linking.asSource)
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function normalizeWidgetInteraction(interaction) {
|
|
1001
|
+
if (!interaction || typeof interaction !== "object") return interaction;
|
|
1002
|
+
const raw = interaction;
|
|
1003
|
+
const access_level = typeof raw.access_level === "string" ? raw.access_level : typeof raw.accessLevel === "string" ? raw.accessLevel : void 0;
|
|
1004
|
+
const linking = parseGristWidgetLinkingInfo(raw.linking);
|
|
1005
|
+
return {
|
|
1006
|
+
...interaction,
|
|
1007
|
+
...access_level ? { access_level } : {},
|
|
1008
|
+
...linking ? { linking } : {}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/sdk/internal/subscriptions.ts
|
|
1013
|
+
var GristEvent = class {
|
|
1014
|
+
constructor(bindOnce) {
|
|
1015
|
+
this.bindOnce = bindOnce;
|
|
1016
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1017
|
+
this.hasValue = false;
|
|
1018
|
+
this.bind = null;
|
|
1019
|
+
}
|
|
1020
|
+
subscribe(listener) {
|
|
1021
|
+
this.listeners.add(listener);
|
|
1022
|
+
if (!this.bind) {
|
|
1023
|
+
this.bind = () => {
|
|
1024
|
+
this.bind = () => {
|
|
1025
|
+
};
|
|
1026
|
+
try {
|
|
1027
|
+
this.bindOnce((value) => {
|
|
1028
|
+
this.hasValue = true;
|
|
1029
|
+
this.last = value;
|
|
1030
|
+
for (const l of this.listeners) l(value);
|
|
1031
|
+
});
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
if (typeof console !== "undefined") {
|
|
1034
|
+
console.error("[lib/grist] failed to subscribe to Grist event:", err);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
this.bind();
|
|
1039
|
+
}
|
|
1040
|
+
if (this.hasValue && this.last !== void 0) {
|
|
1041
|
+
listener(this.last);
|
|
1042
|
+
}
|
|
1043
|
+
return () => {
|
|
1044
|
+
this.listeners.delete(listener);
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
/** Last cached value (or `undefined` if no event has fired yet). */
|
|
1048
|
+
peek() {
|
|
1049
|
+
return this.last;
|
|
1050
|
+
}
|
|
1051
|
+
/** True once at least one event has fired. */
|
|
1052
|
+
get ready() {
|
|
1053
|
+
return this.hasValue;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Test helper: fan out a payload without calling `grist.on*`.
|
|
1057
|
+
* Used to simulate hosts that reuse one record object and mutate it in place.
|
|
1058
|
+
*/
|
|
1059
|
+
deliverForTests(value) {
|
|
1060
|
+
this.hasValue = true;
|
|
1061
|
+
this.last = value;
|
|
1062
|
+
for (const l of this.listeners) l(value);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Test helper: forget the bound state and cached value. Lets a test swap
|
|
1066
|
+
* `window.grist` (e.g. via the emulator's `dispose()` + `createGristEmulator()`)
|
|
1067
|
+
* and re-bind cleanly on the next subscribe.
|
|
1068
|
+
*/
|
|
1069
|
+
_resetForTests() {
|
|
1070
|
+
this.listeners.clear();
|
|
1071
|
+
this.hasValue = false;
|
|
1072
|
+
this.last = void 0;
|
|
1073
|
+
this.bind = null;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
var recordEvent = new GristEvent((emit) => {
|
|
1077
|
+
const deliver = (record, mappings) => {
|
|
1078
|
+
emit({ record, mappings });
|
|
1079
|
+
};
|
|
1080
|
+
grist.onRecord(
|
|
1081
|
+
(record, mappings) => {
|
|
1082
|
+
deliver(record, mappings ?? null);
|
|
1083
|
+
},
|
|
1084
|
+
{ keepEncoded: false }
|
|
1085
|
+
);
|
|
1086
|
+
const fetchSelectedRecord = grist.fetchSelectedRecord ?? grist.docApi?.fetchSelectedRecord;
|
|
1087
|
+
if (typeof grist.on !== "function" || typeof fetchSelectedRecord !== "function") {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
let lastRowId;
|
|
1091
|
+
grist.on("message", async (msg) => {
|
|
1092
|
+
if (!msg || typeof msg !== "object") return;
|
|
1093
|
+
const host = msg;
|
|
1094
|
+
if (!host.tableId || typeof host.rowId !== "number") return;
|
|
1095
|
+
if (host.rowId === lastRowId) return;
|
|
1096
|
+
lastRowId = host.rowId;
|
|
1097
|
+
try {
|
|
1098
|
+
const record = await fetchSelectedRecord(host.rowId, { keepEncoded: false });
|
|
1099
|
+
deliver(record, recordEvent.peek()?.mappings ?? null);
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
if (typeof console !== "undefined") {
|
|
1102
|
+
console.error("[grist-widget-sdk] fetchSelectedRecord on cursor message failed:", err);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
var recordsEvent = new GristEvent((emit) => {
|
|
1108
|
+
grist.onRecords(
|
|
1109
|
+
(records, mappings) => {
|
|
1110
|
+
emit({ records, mappings: mappings ?? null });
|
|
1111
|
+
},
|
|
1112
|
+
{ keepEncoded: false, format: "rows" }
|
|
1113
|
+
);
|
|
1114
|
+
});
|
|
1115
|
+
var optionsEvent = new GristEvent((emit) => {
|
|
1116
|
+
grist.onOptions((options, interaction) => {
|
|
1117
|
+
const normalized = normalizeWidgetInteraction(
|
|
1118
|
+
interaction ?? null
|
|
1119
|
+
);
|
|
1120
|
+
emit({
|
|
1121
|
+
options: options ? { ...options } : null,
|
|
1122
|
+
interaction: normalized
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
var newRecordEvent = new GristEvent((emit) => {
|
|
1127
|
+
if (typeof grist.onNewRecord !== "function") return;
|
|
1128
|
+
grist.onNewRecord((mappings) => {
|
|
1129
|
+
emit({ mappings: mappings ?? null });
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
function resetGristEventsForTests() {
|
|
1133
|
+
recordEvent._resetForTests();
|
|
1134
|
+
recordsEvent._resetForTests();
|
|
1135
|
+
optionsEvent._resetForTests();
|
|
1136
|
+
newRecordEvent._resetForTests();
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1052
1139
|
// src/sdk/internal/handshake/effects/subscriptions.ts
|
|
1053
1140
|
function defaultSubscriptionsBinder(ctx) {
|
|
1054
1141
|
const offs = [];
|
|
@@ -1200,7 +1287,7 @@ function startHeartbeatEffect(env, dispatch, signal, options = {}) {
|
|
|
1200
1287
|
let cancelled = false;
|
|
1201
1288
|
let paused = false;
|
|
1202
1289
|
let intervalTimer = null;
|
|
1203
|
-
let
|
|
1290
|
+
let inFlight3 = false;
|
|
1204
1291
|
let inFlightTimeoutTimer = null;
|
|
1205
1292
|
const clearInterval = () => {
|
|
1206
1293
|
if (intervalTimer !== null) {
|
|
@@ -1222,13 +1309,13 @@ function startHeartbeatEffect(env, dispatch, signal, options = {}) {
|
|
|
1222
1309
|
const ping = () => {
|
|
1223
1310
|
if (cancelled || paused) return;
|
|
1224
1311
|
intervalTimer = null;
|
|
1225
|
-
if (
|
|
1312
|
+
if (inFlight3) {
|
|
1226
1313
|
return;
|
|
1227
1314
|
}
|
|
1228
|
-
|
|
1315
|
+
inFlight3 = true;
|
|
1229
1316
|
inFlightTimeoutTimer = env.setTimeout(() => {
|
|
1230
|
-
if (!
|
|
1231
|
-
|
|
1317
|
+
if (!inFlight3) return;
|
|
1318
|
+
inFlight3 = false;
|
|
1232
1319
|
clearInFlightTimeout();
|
|
1233
1320
|
if (!cancelled) {
|
|
1234
1321
|
dispatch({ type: "HEARTBEAT_FAIL", atMs: env.now() });
|
|
@@ -1236,16 +1323,16 @@ function startHeartbeatEffect(env, dispatch, signal, options = {}) {
|
|
|
1236
1323
|
}
|
|
1237
1324
|
}, pingTimeoutMs);
|
|
1238
1325
|
const handleOk = () => {
|
|
1239
|
-
if (!
|
|
1240
|
-
|
|
1326
|
+
if (!inFlight3) return;
|
|
1327
|
+
inFlight3 = false;
|
|
1241
1328
|
clearInFlightTimeout();
|
|
1242
1329
|
if (cancelled) return;
|
|
1243
1330
|
dispatch({ type: "HEARTBEAT_OK", atMs: env.now() });
|
|
1244
1331
|
scheduleNext();
|
|
1245
1332
|
};
|
|
1246
1333
|
const handleFail = () => {
|
|
1247
|
-
if (!
|
|
1248
|
-
|
|
1334
|
+
if (!inFlight3) return;
|
|
1335
|
+
inFlight3 = false;
|
|
1249
1336
|
clearInFlightTimeout();
|
|
1250
1337
|
if (cancelled) return;
|
|
1251
1338
|
dispatch({ type: "HEARTBEAT_FAIL", atMs: env.now() });
|
|
@@ -1285,7 +1372,7 @@ function startHeartbeatEffect(env, dispatch, signal, options = {}) {
|
|
|
1285
1372
|
cancelled = true;
|
|
1286
1373
|
clearInterval();
|
|
1287
1374
|
clearInFlightTimeout();
|
|
1288
|
-
|
|
1375
|
+
inFlight3 = false;
|
|
1289
1376
|
offVisibility?.();
|
|
1290
1377
|
offOnline?.();
|
|
1291
1378
|
});
|
|
@@ -1295,7 +1382,7 @@ function startHeartbeatEffect(env, dispatch, signal, options = {}) {
|
|
|
1295
1382
|
cancelled = true;
|
|
1296
1383
|
clearInterval();
|
|
1297
1384
|
clearInFlightTimeout();
|
|
1298
|
-
|
|
1385
|
+
inFlight3 = false;
|
|
1299
1386
|
offVisibility?.();
|
|
1300
1387
|
offOnline?.();
|
|
1301
1388
|
},
|
|
@@ -1605,7 +1692,351 @@ function resetEmbeddedHandshakeManagerForTests() {
|
|
|
1605
1692
|
}
|
|
1606
1693
|
}
|
|
1607
1694
|
|
|
1695
|
+
// src/sdk/internal/handshake-manager-context.ts
|
|
1696
|
+
var GristHandshakeManagerContext = createContext(null);
|
|
1697
|
+
function useGristManagerOptional() {
|
|
1698
|
+
return useContext(GristHandshakeManagerContext);
|
|
1699
|
+
}
|
|
1700
|
+
function useGristManager(options) {
|
|
1701
|
+
const fromProvider = useContext(GristHandshakeManagerContext);
|
|
1702
|
+
const optionsRef = useRef(options);
|
|
1703
|
+
optionsRef.current = options;
|
|
1704
|
+
const embeddedRef = useRef(null);
|
|
1705
|
+
const ownsEmbeddedRef = useRef(false);
|
|
1706
|
+
if (fromProvider) {
|
|
1707
|
+
return fromProvider;
|
|
1708
|
+
}
|
|
1709
|
+
if (embeddedRef.current === null) {
|
|
1710
|
+
embeddedRef.current = acquireEmbeddedHandshakeManager(
|
|
1711
|
+
buildManagerOptionsFromGristOptions(optionsRef.current)
|
|
1712
|
+
);
|
|
1713
|
+
ownsEmbeddedRef.current = true;
|
|
1714
|
+
}
|
|
1715
|
+
useEffect(() => {
|
|
1716
|
+
if (!ownsEmbeddedRef.current) return;
|
|
1717
|
+
return () => {
|
|
1718
|
+
releaseEmbeddedHandshakeManager();
|
|
1719
|
+
embeddedRef.current = null;
|
|
1720
|
+
ownsEmbeddedRef.current = false;
|
|
1721
|
+
};
|
|
1722
|
+
}, []);
|
|
1723
|
+
return embeddedRef.current;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/sdk/internal/hooks/use-grist-core.ts
|
|
1727
|
+
function computeAvailabilityFromSnapshot(snapshot) {
|
|
1728
|
+
const { phase } = snapshot.lifecycle;
|
|
1729
|
+
if (phase === "idle" || phase === "detecting") return null;
|
|
1730
|
+
if (phase === "terminated") {
|
|
1731
|
+
const reason = snapshot.lifecycle.reason.kind;
|
|
1732
|
+
if (reason === "not_embedded" || reason === "script_timeout") return false;
|
|
1733
|
+
return true;
|
|
1734
|
+
}
|
|
1735
|
+
return true;
|
|
1736
|
+
}
|
|
1737
|
+
function useGristCore(options) {
|
|
1738
|
+
const manager = useGristManager(options);
|
|
1739
|
+
return useGristCoreFromManager(manager);
|
|
1740
|
+
}
|
|
1741
|
+
function useGristCoreFromManager(manager) {
|
|
1742
|
+
const snapshot = useSyncExternalStore(
|
|
1743
|
+
manager.subscribe,
|
|
1744
|
+
manager.getSnapshot,
|
|
1745
|
+
initialGristSnapshot
|
|
1746
|
+
);
|
|
1747
|
+
const status = deriveStatus(snapshot);
|
|
1748
|
+
const error = deriveErrorMessage(snapshot);
|
|
1749
|
+
const docApi = snapshot.lifecycle.phase === "online" && typeof grist !== "undefined" ? grist.docApi ?? null : null;
|
|
1750
|
+
const isAvailable = computeAvailabilityFromSnapshot(snapshot);
|
|
1751
|
+
const reload = useCallback(async () => {
|
|
1752
|
+
manager.reload();
|
|
1753
|
+
}, [manager]);
|
|
1754
|
+
const capabilities = useMemo(() => deriveCapabilities(snapshot), [snapshot]);
|
|
1755
|
+
return useMemo(
|
|
1756
|
+
() => ({
|
|
1757
|
+
status,
|
|
1758
|
+
isAvailable,
|
|
1759
|
+
isReady: status === "ready",
|
|
1760
|
+
error,
|
|
1761
|
+
docApi,
|
|
1762
|
+
capabilities,
|
|
1763
|
+
reload
|
|
1764
|
+
}),
|
|
1765
|
+
[status, isAvailable, error, docApi, capabilities, reload]
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/sdk/low-level-hooks/use-grist-ready.ts
|
|
1770
|
+
function useGristReady(options) {
|
|
1771
|
+
const { isAvailable, docApi, error, reload } = useGristCore(options);
|
|
1772
|
+
return { isAvailable, docApi, error, reload };
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/sdk/mid-level-hooks/use-grist-widget-options-from-context.ts
|
|
1776
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState } from "react";
|
|
1777
|
+
|
|
1778
|
+
// src/sdk/internal/contexts.tsx
|
|
1779
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
1780
|
+
|
|
1781
|
+
// src/sdk/internal/merge-grist-result.ts
|
|
1782
|
+
function mergeGristResultFromSlices(slices) {
|
|
1783
|
+
const { core, selection, currentTable, writes, reads, widgetOptionsApi, linking, attachmentsRest, theme, sectionApi, suppressAlerts, allowSelectBy } = slices;
|
|
1784
|
+
return {
|
|
1785
|
+
status: core.status,
|
|
1786
|
+
isAvailable: core.isAvailable,
|
|
1787
|
+
isReady: core.isReady,
|
|
1788
|
+
error: core.error,
|
|
1789
|
+
capabilities: core.capabilities,
|
|
1790
|
+
record: selection.record,
|
|
1791
|
+
records: selection.records,
|
|
1792
|
+
mappedRecord: selection.mappedRecord,
|
|
1793
|
+
mappings: selection.mappings,
|
|
1794
|
+
recordsMappings: selection.recordsMappings,
|
|
1795
|
+
columnMappingStatus: selection.columnMappingStatus,
|
|
1796
|
+
mode: selection.mode,
|
|
1797
|
+
resolveMappedColumnId: selection.resolveMappedColumnId,
|
|
1798
|
+
mapBack: selection.mapBack,
|
|
1799
|
+
mapBackSkipped: selection.mapBackSkipped,
|
|
1800
|
+
isNewRecord: selection.isNewRecord,
|
|
1801
|
+
newRecordMappings: selection.newRecordMappings,
|
|
1802
|
+
currentTableId: currentTable.currentTableId,
|
|
1803
|
+
currentTableLoading: currentTable.currentTableLoading,
|
|
1804
|
+
refreshCurrentTable: currentTable.refreshCurrentTable,
|
|
1805
|
+
table: writes.table,
|
|
1806
|
+
getTable: writes.getTable,
|
|
1807
|
+
actionStatus: writes.actionStatus,
|
|
1808
|
+
actionError: writes.actionError,
|
|
1809
|
+
applyActions: writes.applyActions,
|
|
1810
|
+
reload: core.reload,
|
|
1811
|
+
fetchTable: reads.fetchTable,
|
|
1812
|
+
fetchTableRows: reads.fetchTableRows,
|
|
1813
|
+
fetchRow: reads.fetchRow,
|
|
1814
|
+
listTables: reads.listTables,
|
|
1815
|
+
listColumns: reads.listColumns,
|
|
1816
|
+
getDocName: reads.getDocName,
|
|
1817
|
+
buildReplicaDocumentFromDocApi: reads.buildReplicaDocumentFromDocApi,
|
|
1818
|
+
getAttachmentUrl: attachmentsRest.getAttachmentUrl,
|
|
1819
|
+
fetchAttachmentBase64: attachmentsRest.fetchAttachmentBase64,
|
|
1820
|
+
fetchAttachmentBlob: attachmentsRest.fetchAttachmentBlob,
|
|
1821
|
+
widgetOptions: selection.widgetOptions,
|
|
1822
|
+
widgetInteraction: selection.widgetInteraction,
|
|
1823
|
+
getWidgetOptions: widgetOptionsApi.getWidgetOptions,
|
|
1824
|
+
getWidgetOption: widgetOptionsApi.getWidgetOption,
|
|
1825
|
+
setWidgetOption: widgetOptionsApi.setWidgetOption,
|
|
1826
|
+
setWidgetOptions: widgetOptionsApi.setWidgetOptions,
|
|
1827
|
+
patchWidgetOptions: widgetOptionsApi.patchWidgetOptions,
|
|
1828
|
+
clearWidgetOptions: widgetOptionsApi.clearWidgetOptions,
|
|
1829
|
+
fetchSelectedTable: reads.fetchSelectedTable,
|
|
1830
|
+
fetchSelectedRecord: reads.fetchSelectedRecord,
|
|
1831
|
+
readError: reads.readError ?? attachmentsRest.readError,
|
|
1832
|
+
setCursorPosition: linking.setCursorPosition,
|
|
1833
|
+
setLinkedRowSelection: linking.setLinkedRowSelection,
|
|
1834
|
+
getAccessToken: attachmentsRest.getAccessToken,
|
|
1835
|
+
fetchWithAuth: attachmentsRest.fetchWithAuth,
|
|
1836
|
+
uploadAttachment: attachmentsRest.uploadAttachment,
|
|
1837
|
+
theme: theme.theme,
|
|
1838
|
+
configure: sectionApi.configure,
|
|
1839
|
+
refreshMappings: sectionApi.refreshMappings,
|
|
1840
|
+
suppressAlerts,
|
|
1841
|
+
allowSelectBy
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// src/sdk/internal/contexts.tsx
|
|
1846
|
+
var GristCoreContext = createContext2(null);
|
|
1847
|
+
var GristSelectionContext = createContext2(null);
|
|
1848
|
+
var GristCurrentTableContext = createContext2(null);
|
|
1849
|
+
var GristWritesContext = createContext2(null);
|
|
1850
|
+
var GristReadsContext = createContext2(null);
|
|
1851
|
+
var GristWidgetOptionsApiContext = createContext2(null);
|
|
1852
|
+
var GristLinkingContext = createContext2(null);
|
|
1853
|
+
var GristAttachmentsRestContext = createContext2(null);
|
|
1854
|
+
var GristThemeContext = createContext2(null);
|
|
1855
|
+
var GristSectionApiContext = createContext2(null);
|
|
1856
|
+
function useRequiredContext(ctx, name) {
|
|
1857
|
+
const value = useContext2(ctx);
|
|
1858
|
+
if (!value) {
|
|
1859
|
+
throw new Error(`${name} must be used inside <GristWidgetProvider>`);
|
|
1860
|
+
}
|
|
1861
|
+
return value;
|
|
1862
|
+
}
|
|
1863
|
+
function useGristCoreContext() {
|
|
1864
|
+
return useRequiredContext(GristCoreContext, "useGristCoreContext()");
|
|
1865
|
+
}
|
|
1866
|
+
function useGristSelectionContext() {
|
|
1867
|
+
return useRequiredContext(GristSelectionContext, "useGristSelectionContext()");
|
|
1868
|
+
}
|
|
1869
|
+
function useGristCurrentTableContext() {
|
|
1870
|
+
return useRequiredContext(GristCurrentTableContext, "useGristCurrentTableContext()");
|
|
1871
|
+
}
|
|
1872
|
+
function useGristWritesContext() {
|
|
1873
|
+
return useRequiredContext(GristWritesContext, "useGristWritesContext()");
|
|
1874
|
+
}
|
|
1875
|
+
function useGristReadsContext() {
|
|
1876
|
+
return useRequiredContext(GristReadsContext, "useGristReadsContext()");
|
|
1877
|
+
}
|
|
1878
|
+
function useGristWidgetOptionsApiContext() {
|
|
1879
|
+
return useRequiredContext(GristWidgetOptionsApiContext, "useGristWidgetOptionsApiContext()");
|
|
1880
|
+
}
|
|
1881
|
+
function useGristLinkingContext() {
|
|
1882
|
+
return useRequiredContext(GristLinkingContext, "useGristLinkingContext()");
|
|
1883
|
+
}
|
|
1884
|
+
function useGristAttachmentsRestContext() {
|
|
1885
|
+
return useRequiredContext(GristAttachmentsRestContext, "useGristAttachmentsRestContext()");
|
|
1886
|
+
}
|
|
1887
|
+
function useGristThemeContext() {
|
|
1888
|
+
return useRequiredContext(GristThemeContext, "useGristThemeContext()");
|
|
1889
|
+
}
|
|
1890
|
+
function useGristSectionApiContext() {
|
|
1891
|
+
return useRequiredContext(GristSectionApiContext, "useGristSectionApiContext()");
|
|
1892
|
+
}
|
|
1893
|
+
function useGristFromProvider() {
|
|
1894
|
+
const core = useGristCoreContext();
|
|
1895
|
+
const selection = useGristSelectionContext();
|
|
1896
|
+
const currentTable = useGristCurrentTableContext();
|
|
1897
|
+
const writes = useGristWritesContext();
|
|
1898
|
+
const reads = useGristReadsContext();
|
|
1899
|
+
const widgetOptionsApi = useGristWidgetOptionsApiContext();
|
|
1900
|
+
const linking = useGristLinkingContext();
|
|
1901
|
+
const attachmentsRest = useGristAttachmentsRestContext();
|
|
1902
|
+
const theme = useGristThemeContext();
|
|
1903
|
+
const sectionApi = useGristSectionApiContext();
|
|
1904
|
+
return useMemo2(
|
|
1905
|
+
() => mergeGristResultFromSlices({
|
|
1906
|
+
core,
|
|
1907
|
+
selection,
|
|
1908
|
+
currentTable,
|
|
1909
|
+
writes,
|
|
1910
|
+
reads,
|
|
1911
|
+
widgetOptionsApi,
|
|
1912
|
+
linking,
|
|
1913
|
+
attachmentsRest,
|
|
1914
|
+
theme,
|
|
1915
|
+
sectionApi
|
|
1916
|
+
}),
|
|
1917
|
+
[
|
|
1918
|
+
core,
|
|
1919
|
+
selection,
|
|
1920
|
+
currentTable,
|
|
1921
|
+
writes,
|
|
1922
|
+
reads,
|
|
1923
|
+
widgetOptionsApi,
|
|
1924
|
+
linking,
|
|
1925
|
+
attachmentsRest,
|
|
1926
|
+
theme,
|
|
1927
|
+
sectionApi
|
|
1928
|
+
]
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/sdk/mid-level-hooks/use-grist-widget-options-from-context.ts
|
|
1933
|
+
function mergeWithDefaults(defaults, stored, namespace) {
|
|
1934
|
+
if (stored === null || stored === void 0) return { ...defaults };
|
|
1935
|
+
const source = namespace ? stored[namespace] : stored;
|
|
1936
|
+
if (!source || typeof source !== "object") return { ...defaults };
|
|
1937
|
+
return { ...defaults, ...source };
|
|
1938
|
+
}
|
|
1939
|
+
function useGristWidgetOptionsFromContext(config) {
|
|
1940
|
+
const debounceMs = config.debounceMs ?? 250;
|
|
1941
|
+
const selection = useGristSelectionContext();
|
|
1942
|
+
const optionsApi = useGristWidgetOptionsApiContext();
|
|
1943
|
+
const defaultsRef = useRef2(config.defaults);
|
|
1944
|
+
useEffect2(() => {
|
|
1945
|
+
defaultsRef.current = config.defaults;
|
|
1946
|
+
}, [config.defaults]);
|
|
1947
|
+
const rawOptions = selection.widgetOptions;
|
|
1948
|
+
const hasReceived = rawOptions !== null;
|
|
1949
|
+
const merged = useMemo3(
|
|
1950
|
+
() => mergeWithDefaults(defaultsRef.current, rawOptions, config.namespace),
|
|
1951
|
+
[rawOptions, config.namespace]
|
|
1952
|
+
);
|
|
1953
|
+
const [localOverride, setLocalOverride] = useState(null);
|
|
1954
|
+
const options = localOverride ?? merged;
|
|
1955
|
+
useEffect2(() => {
|
|
1956
|
+
setLocalOverride(null);
|
|
1957
|
+
}, [rawOptions]);
|
|
1958
|
+
const debounceRef = useRef2(null);
|
|
1959
|
+
const flushWrite = useCallback2(
|
|
1960
|
+
async (value) => {
|
|
1961
|
+
if (config.namespace) {
|
|
1962
|
+
await optionsApi.setWidgetOption(config.namespace, value);
|
|
1963
|
+
} else {
|
|
1964
|
+
await optionsApi.setWidgetOptions({ ...value });
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
[config.namespace, optionsApi]
|
|
1968
|
+
);
|
|
1969
|
+
const scheduleWrite = useCallback2(
|
|
1970
|
+
(value) => {
|
|
1971
|
+
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
1972
|
+
debounceRef.current = window.setTimeout(() => {
|
|
1973
|
+
debounceRef.current = null;
|
|
1974
|
+
void flushWrite(value);
|
|
1975
|
+
}, debounceMs);
|
|
1976
|
+
},
|
|
1977
|
+
[debounceMs, flushWrite]
|
|
1978
|
+
);
|
|
1979
|
+
useEffect2(() => {
|
|
1980
|
+
return () => {
|
|
1981
|
+
if (debounceRef.current) window.clearTimeout(debounceRef.current);
|
|
1982
|
+
};
|
|
1983
|
+
}, []);
|
|
1984
|
+
const setOptions = useCallback2(
|
|
1985
|
+
(next) => {
|
|
1986
|
+
setLocalOverride(next);
|
|
1987
|
+
scheduleWrite(next);
|
|
1988
|
+
},
|
|
1989
|
+
[scheduleWrite]
|
|
1990
|
+
);
|
|
1991
|
+
const patchOptions = useCallback2(
|
|
1992
|
+
(patch) => {
|
|
1993
|
+
setLocalOverride((prev) => {
|
|
1994
|
+
const base = prev ?? merged;
|
|
1995
|
+
const next = { ...base, ...patch };
|
|
1996
|
+
scheduleWrite(next);
|
|
1997
|
+
return next;
|
|
1998
|
+
});
|
|
1999
|
+
},
|
|
2000
|
+
[merged, scheduleWrite]
|
|
2001
|
+
);
|
|
2002
|
+
const reset = useCallback2(async () => {
|
|
2003
|
+
if (debounceRef.current) {
|
|
2004
|
+
window.clearTimeout(debounceRef.current);
|
|
2005
|
+
debounceRef.current = null;
|
|
2006
|
+
}
|
|
2007
|
+
const next = { ...defaultsRef.current };
|
|
2008
|
+
setLocalOverride(null);
|
|
2009
|
+
await flushWrite(next);
|
|
2010
|
+
}, [flushWrite]);
|
|
2011
|
+
return useMemo3(
|
|
2012
|
+
() => ({
|
|
2013
|
+
options,
|
|
2014
|
+
loading: !hasReceived,
|
|
2015
|
+
setOptions,
|
|
2016
|
+
patchOptions,
|
|
2017
|
+
reset
|
|
2018
|
+
}),
|
|
2019
|
+
[options, hasReceived, setOptions, patchOptions, reset]
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// src/sdk/internal/hooks/use-rpc-heartbeat-coalesce.ts
|
|
2024
|
+
import { useCallback as useCallback3 } from "react";
|
|
2025
|
+
function useRpcHeartbeatCoalesce() {
|
|
2026
|
+
const manager = useGristManagerOptional();
|
|
2027
|
+
return useCallback3(
|
|
2028
|
+
async (fn) => {
|
|
2029
|
+
const result = await fn();
|
|
2030
|
+
manager?.recordRpcSuccess();
|
|
2031
|
+
return result;
|
|
2032
|
+
},
|
|
2033
|
+
[manager]
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1608
2037
|
export {
|
|
2038
|
+
getGristAccessToken,
|
|
2039
|
+
fetchWithGristAuth,
|
|
1609
2040
|
validateColumnMappings,
|
|
1610
2041
|
initialGristSnapshot,
|
|
1611
2042
|
deriveStatus,
|
|
@@ -1617,14 +2048,37 @@ export {
|
|
|
1617
2048
|
ensureGristReady,
|
|
1618
2049
|
resetGristReadySingleton,
|
|
1619
2050
|
resetGristReadyCacheForTests,
|
|
2051
|
+
buildManagerOptionsFromGristOptions,
|
|
1620
2052
|
recordEvent,
|
|
1621
2053
|
recordsEvent,
|
|
1622
2054
|
optionsEvent,
|
|
1623
2055
|
newRecordEvent,
|
|
1624
2056
|
resetGristEventsForTests,
|
|
1625
2057
|
createGristHandshakeManager,
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
2058
|
+
resetEmbeddedHandshakeManagerForTests,
|
|
2059
|
+
GristHandshakeManagerContext,
|
|
2060
|
+
useGristManagerOptional,
|
|
2061
|
+
useGristManager,
|
|
2062
|
+
mergeGristResultFromSlices,
|
|
2063
|
+
GristCoreContext,
|
|
2064
|
+
GristSelectionContext,
|
|
2065
|
+
GristCurrentTableContext,
|
|
2066
|
+
GristWritesContext,
|
|
2067
|
+
GristReadsContext,
|
|
2068
|
+
GristWidgetOptionsApiContext,
|
|
2069
|
+
GristLinkingContext,
|
|
2070
|
+
GristAttachmentsRestContext,
|
|
2071
|
+
GristThemeContext,
|
|
2072
|
+
GristSectionApiContext,
|
|
2073
|
+
useGristCoreContext,
|
|
2074
|
+
useGristSelectionContext,
|
|
2075
|
+
useGristCurrentTableContext,
|
|
2076
|
+
useGristWritesContext,
|
|
2077
|
+
useGristThemeContext,
|
|
2078
|
+
useGristFromProvider,
|
|
2079
|
+
useGristCore,
|
|
2080
|
+
useRpcHeartbeatCoalesce,
|
|
2081
|
+
useGristReady,
|
|
2082
|
+
useGristWidgetOptionsFromContext
|
|
1629
2083
|
};
|
|
1630
|
-
//# sourceMappingURL=chunk-
|
|
2084
|
+
//# sourceMappingURL=chunk-NO6FYX33.js.map
|