grist-widget-sdk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +79 -208
- package/dist/app.cjs +183 -65
- package/dist/app.cjs.map +1 -1
- package/dist/app.d.cts +5 -100
- package/dist/app.d.ts +5 -100
- package/dist/app.js +4 -2
- package/dist/{chunk-P6VTEKWO.js → chunk-LSALXIGM.js} +82 -10
- package/dist/chunk-LSALXIGM.js.map +1 -0
- package/dist/{chunk-O7NY46QY.js → chunk-MAPAWVQC.js} +88 -20
- package/dist/chunk-MAPAWVQC.js.map +1 -0
- package/dist/{chunk-B5ZZNLVS.js → chunk-OXA2VITC.js} +5 -1
- package/dist/chunk-OXA2VITC.js.map +1 -0
- package/dist/{use-grist-BK0eke9K.d.cts → define-widget-B1FIvI-h.d.cts} +179 -104
- package/dist/{use-grist-C5DMZ3wU.d.ts → define-widget-B9gEtJP0.d.ts} +179 -104
- package/dist/emulator/testing.cjs +2390 -2243
- package/dist/emulator/testing.cjs.map +1 -1
- package/dist/emulator/testing.d.cts +27 -9
- package/dist/emulator/testing.d.ts +27 -9
- package/dist/emulator/testing.js +74 -55
- package/dist/emulator/testing.js.map +1 -1
- package/dist/emulator.cjs +4 -0
- package/dist/emulator.cjs.map +1 -1
- package/dist/emulator.d.cts +3 -3
- package/dist/emulator.d.ts +3 -3
- package/dist/emulator.js +1 -1
- package/dist/index.cjs +133 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -1
- package/dist/mount-grist-widget-CkB-3iG_.d.ts +137 -0
- package/dist/mount-grist-widget-Dtelqao_.d.cts +137 -0
- package/package.json +1 -1
- package/dist/chunk-B5ZZNLVS.js.map +0 -1
- package/dist/chunk-O7NY46QY.js.map +0 -1
- package/dist/chunk-P6VTEKWO.js.map +0 -1
package/dist/emulator.d.cts
CHANGED
|
@@ -96,9 +96,9 @@ declare class EmulatorActionError extends Error {
|
|
|
96
96
|
/**
|
|
97
97
|
* Resolves the `mappings` payload that `CustomSectionAPI.mappings()` returns.
|
|
98
98
|
*
|
|
99
|
-
* The
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
* The emulator does **not** infer logical → real columns from names, as
|
|
100
|
+
* real Grist doesn't: mappings come only from `state.selection.mappings`
|
|
101
|
+
* (a test's `emulator.setColumnMappings(...)`). When unset, returns `null`.
|
|
102
102
|
*/
|
|
103
103
|
|
|
104
104
|
declare function resolveMappings(state: EmulatorDocumentState): GristWidgetColumnMap | null;
|
package/dist/emulator.d.ts
CHANGED
|
@@ -96,9 +96,9 @@ declare class EmulatorActionError extends Error {
|
|
|
96
96
|
/**
|
|
97
97
|
* Resolves the `mappings` payload that `CustomSectionAPI.mappings()` returns.
|
|
98
98
|
*
|
|
99
|
-
* The
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
* The emulator does **not** infer logical → real columns from names, as
|
|
100
|
+
* real Grist doesn't: mappings come only from `state.selection.mappings`
|
|
101
|
+
* (a test's `emulator.setColumnMappings(...)`). When unset, returns `null`.
|
|
102
102
|
*/
|
|
103
103
|
|
|
104
104
|
declare function resolveMappings(state: EmulatorDocumentState): GristWidgetColumnMap | null;
|
package/dist/emulator.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,8 @@ __export(src_exports, {
|
|
|
24
24
|
GristWidgetProvider: () => GristWidgetProvider,
|
|
25
25
|
checkGristCell: () => checkGristCell,
|
|
26
26
|
decodeGristValue: () => decodeGristValue,
|
|
27
|
+
defineGristWidget: () => defineGristWidget,
|
|
28
|
+
encodeGristCell: () => encodeGristCell,
|
|
27
29
|
encodeGristValue: () => encodeGristValue,
|
|
28
30
|
extractGristAttachmentId: () => extractGristAttachmentId,
|
|
29
31
|
extractGristAttachmentIdsFromCell: () => extractGristAttachmentIdsFromCell,
|
|
@@ -858,16 +860,24 @@ function createGristApi(runtime) {
|
|
|
858
860
|
throw err;
|
|
859
861
|
}
|
|
860
862
|
};
|
|
861
|
-
|
|
863
|
+
let running = 0;
|
|
864
|
+
let failure = null;
|
|
865
|
+
const write = (fn) => {
|
|
866
|
+
if (running++ === 0) failure = null;
|
|
862
867
|
runtime.update({ actionStatus: "running", actionError: null });
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
868
|
+
const done = (async () => {
|
|
869
|
+
try {
|
|
870
|
+
return await fn();
|
|
871
|
+
} catch (err) {
|
|
872
|
+
failure = message(err);
|
|
873
|
+
throw err;
|
|
874
|
+
} finally {
|
|
875
|
+
if (--running === 0) runtime.update(failure ? { actionStatus: "error", actionError: failure } : { actionStatus: "idle" });
|
|
876
|
+
}
|
|
877
|
+
})();
|
|
878
|
+
done.catch(() => {
|
|
879
|
+
});
|
|
880
|
+
return done;
|
|
871
881
|
};
|
|
872
882
|
const tableOps = (resolve) => ({
|
|
873
883
|
getTableId: () => resolve().getTableId(),
|
|
@@ -972,58 +982,6 @@ function validateColumnMappings(spec, mappings) {
|
|
|
972
982
|
};
|
|
973
983
|
}
|
|
974
984
|
|
|
975
|
-
// src/sdk/runtime/derive.ts
|
|
976
|
-
var ACCESS_RANK = { none: 0, "read table": 1, full: 2 };
|
|
977
|
-
function parseAccess(raw) {
|
|
978
|
-
return raw === "none" || raw === "read table" || raw === "full" ? raw : null;
|
|
979
|
-
}
|
|
980
|
-
function accessLabel(level) {
|
|
981
|
-
return level === "full" ? "Full document access" : level === "read table" ? "Read selected table" : "No document access";
|
|
982
|
-
}
|
|
983
|
-
function accessDenied(state) {
|
|
984
|
-
return state.grantedAccess !== null && ACCESS_RANK[state.grantedAccess] < ACCESS_RANK[state.declaredAccess];
|
|
985
|
-
}
|
|
986
|
-
function deriveStatus(state) {
|
|
987
|
-
switch (state.connection) {
|
|
988
|
-
case "connecting":
|
|
989
|
-
return "booting";
|
|
990
|
-
case "not-embedded":
|
|
991
|
-
case "unavailable":
|
|
992
|
-
return "unavailable";
|
|
993
|
-
case "error":
|
|
994
|
-
return "error";
|
|
995
|
-
case "ready":
|
|
996
|
-
return accessDenied(state) ? "error" : "ready";
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function deriveError(state) {
|
|
1000
|
-
if (state.connection === "not-embedded") return "This page is not embedded in a Grist document.";
|
|
1001
|
-
if (state.connection === "unavailable") return "The Grist plugin API (grist-plugin-api.js) did not load.";
|
|
1002
|
-
if (state.connection === "error") return state.connectionError;
|
|
1003
|
-
if (state.connection === "ready" && accessDenied(state)) {
|
|
1004
|
-
return `Grist granted "${accessLabel(state.grantedAccess)}"; this widget needs "${accessLabel(state.declaredAccess)}". Set the access level in the Custom widget settings.`;
|
|
1005
|
-
}
|
|
1006
|
-
return null;
|
|
1007
|
-
}
|
|
1008
|
-
var MAPPINGS_OK = Object.freeze({ ok: true, pending: false, missing: [], emptyMultiples: [] });
|
|
1009
|
-
var MAPPINGS_PENDING = Object.freeze({ ok: true, pending: true, missing: [], emptyMultiples: [] });
|
|
1010
|
-
function deriveColumnMappingStatus(columns, mappings) {
|
|
1011
|
-
if (!columns?.length) return MAPPINGS_OK;
|
|
1012
|
-
if (mappings === null) return MAPPINGS_PENDING;
|
|
1013
|
-
return validateColumnMappings(columns, mappings);
|
|
1014
|
-
}
|
|
1015
|
-
function mapBackPatch(patch, mappings) {
|
|
1016
|
-
const fields = {};
|
|
1017
|
-
const skipped = [];
|
|
1018
|
-
for (const [name, value] of Object.entries(patch)) {
|
|
1019
|
-
if (value === void 0) continue;
|
|
1020
|
-
const real = mappings[name];
|
|
1021
|
-
if (Array.isArray(real)) skipped.push(name);
|
|
1022
|
-
else fields[typeof real === "string" && real ? real : name] = value;
|
|
1023
|
-
}
|
|
1024
|
-
return { fields, skipped };
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
985
|
// src/sdk/runtime/cells.ts
|
|
1028
986
|
var ok = (value) => ({ ok: true, value });
|
|
1029
987
|
var bad = (message2) => ({ ok: false, message: message2 });
|
|
@@ -1087,10 +1045,13 @@ function checkOne(base, value) {
|
|
|
1087
1045
|
return ok(value);
|
|
1088
1046
|
}
|
|
1089
1047
|
}
|
|
1048
|
+
function basesOf(type) {
|
|
1049
|
+
return (type ?? "Any").split(",").map((t) => t.trim().split(":")[0]).filter(Boolean);
|
|
1050
|
+
}
|
|
1090
1051
|
function checkGristCell(type, value) {
|
|
1091
1052
|
const error = formulaError(value);
|
|
1092
1053
|
if (error) return bad(`formula error (${error})`);
|
|
1093
|
-
const bases = (type
|
|
1054
|
+
const bases = basesOf(type);
|
|
1094
1055
|
if (bases.length === 0 || bases.includes("Any")) return ok(value === void 0 ? null : value);
|
|
1095
1056
|
let first = null;
|
|
1096
1057
|
for (const base of bases) {
|
|
@@ -1100,6 +1061,19 @@ function checkGristCell(type, value) {
|
|
|
1100
1061
|
}
|
|
1101
1062
|
return first;
|
|
1102
1063
|
}
|
|
1064
|
+
var LIST_BASES = ["ChoiceList", "RefList", "Attachments"];
|
|
1065
|
+
function encodeGristCell(type, value) {
|
|
1066
|
+
if (value === null || value === void 0) return value;
|
|
1067
|
+
const bases = basesOf(type);
|
|
1068
|
+
const accepts = (...names) => names.some((n) => bases.includes(n));
|
|
1069
|
+
if (value instanceof Date) return accepts("Date", "DateTime") ? Math.floor(value.getTime() / 1e3) : value;
|
|
1070
|
+
if (Array.isArray(value)) {
|
|
1071
|
+
if (!accepts(...LIST_BASES) || value[0] === "L") return value;
|
|
1072
|
+
return ["L", ...value.map((item) => rowIdOf(item) ?? item)];
|
|
1073
|
+
}
|
|
1074
|
+
if (accepts("Ref") && typeof value === "object") return rowIdOf(value) ?? value;
|
|
1075
|
+
return value;
|
|
1076
|
+
}
|
|
1103
1077
|
var nameOf = (c) => typeof c === "string" ? c : c.name;
|
|
1104
1078
|
var typeOf = (c) => typeof c === "string" ? void 0 : c.type;
|
|
1105
1079
|
var isOptional = (c) => typeof c === "object" && c.optional === true;
|
|
@@ -1140,6 +1114,62 @@ function mapAndCheck(record, columns, mappings, schema) {
|
|
|
1140
1114
|
return { value: out, issues };
|
|
1141
1115
|
}
|
|
1142
1116
|
|
|
1117
|
+
// src/sdk/runtime/derive.ts
|
|
1118
|
+
var ACCESS_RANK = { none: 0, "read table": 1, full: 2 };
|
|
1119
|
+
function parseAccess(raw) {
|
|
1120
|
+
return raw === "none" || raw === "read table" || raw === "full" ? raw : null;
|
|
1121
|
+
}
|
|
1122
|
+
function accessLabel(level) {
|
|
1123
|
+
return level === "full" ? "Full document access" : level === "read table" ? "Read selected table" : "No document access";
|
|
1124
|
+
}
|
|
1125
|
+
function accessDenied(state) {
|
|
1126
|
+
return state.grantedAccess !== null && ACCESS_RANK[state.grantedAccess] < ACCESS_RANK[state.declaredAccess];
|
|
1127
|
+
}
|
|
1128
|
+
function deriveStatus(state) {
|
|
1129
|
+
switch (state.connection) {
|
|
1130
|
+
case "connecting":
|
|
1131
|
+
return "booting";
|
|
1132
|
+
case "not-embedded":
|
|
1133
|
+
case "unavailable":
|
|
1134
|
+
return "unavailable";
|
|
1135
|
+
case "error":
|
|
1136
|
+
return "error";
|
|
1137
|
+
case "ready":
|
|
1138
|
+
return accessDenied(state) ? "error" : "ready";
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function deriveError(state) {
|
|
1142
|
+
if (state.connection === "not-embedded") return "This page is not embedded in a Grist document.";
|
|
1143
|
+
if (state.connection === "unavailable") return "The Grist plugin API (grist-plugin-api.js) did not load.";
|
|
1144
|
+
if (state.connection === "error") return state.connectionError;
|
|
1145
|
+
if (state.connection === "ready" && accessDenied(state)) {
|
|
1146
|
+
return `Grist granted "${accessLabel(state.grantedAccess)}"; this widget needs "${accessLabel(state.declaredAccess)}". Set the access level in the Custom widget settings.`;
|
|
1147
|
+
}
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
var MAPPINGS_OK = Object.freeze({ ok: true, pending: false, missing: [], emptyMultiples: [] });
|
|
1151
|
+
var MAPPINGS_PENDING = Object.freeze({ ok: true, pending: true, missing: [], emptyMultiples: [] });
|
|
1152
|
+
function deriveColumnMappingStatus(columns, mappings) {
|
|
1153
|
+
if (!columns?.length) return MAPPINGS_OK;
|
|
1154
|
+
if (mappings === null) return MAPPINGS_PENDING;
|
|
1155
|
+
return validateColumnMappings(columns, mappings);
|
|
1156
|
+
}
|
|
1157
|
+
function mapBackPatch(patch, mappings, columns) {
|
|
1158
|
+
const fields = {};
|
|
1159
|
+
const skipped = [];
|
|
1160
|
+
for (const [name, value] of Object.entries(patch)) {
|
|
1161
|
+
if (value === void 0) continue;
|
|
1162
|
+
const real = mappings[name];
|
|
1163
|
+
if (Array.isArray(real)) {
|
|
1164
|
+
skipped.push(name);
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
const declared = columns?.find((c) => nameOf(c) === name);
|
|
1168
|
+
fields[typeof real === "string" && real ? real : name] = declared ? encodeGristCell(typeOf(declared), value) : value;
|
|
1169
|
+
}
|
|
1170
|
+
return { fields, skipped };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1143
1173
|
// src/sdk/helpers/grist-widget-interaction.ts
|
|
1144
1174
|
function parseGristWidgetLinkingInfo(raw) {
|
|
1145
1175
|
if (!raw || typeof raw !== "object" || !("asTarget" in raw)) return void 0;
|
|
@@ -1489,7 +1519,27 @@ function useMapped(state, options) {
|
|
|
1489
1519
|
return { record: selected.value, records: mappedRecords, issues: issues.length ? issues : NO_ISSUES };
|
|
1490
1520
|
}, [record, records, mappings, columns, schema]);
|
|
1491
1521
|
}
|
|
1492
|
-
function
|
|
1522
|
+
function recordWrites(connection) {
|
|
1523
|
+
const { runtime, api } = connection;
|
|
1524
|
+
const fieldsOf = (patch) => {
|
|
1525
|
+
const { id: _id, ...rest } = patch;
|
|
1526
|
+
return mapBackPatch(rest, runtime.getState().mappings ?? EMPTY_MAPPINGS, runtime.options.columns).fields;
|
|
1527
|
+
};
|
|
1528
|
+
const updateRecord = ((a, b) => {
|
|
1529
|
+
const rowId = typeof a === "number" ? a : runtime.getState().record?.id;
|
|
1530
|
+
const patch = typeof a === "number" ? b ?? {} : a;
|
|
1531
|
+
if (typeof rowId !== "number") {
|
|
1532
|
+
return Promise.reject(new Error("updateRecord(): no row is selected. Pass the row id: updateRecord(rowId, patch)."));
|
|
1533
|
+
}
|
|
1534
|
+
return api.table.update({ id: rowId, fields: fieldsOf(patch) });
|
|
1535
|
+
});
|
|
1536
|
+
const addRecord = async (patch) => {
|
|
1537
|
+
const created = await api.table.create({ fields: fieldsOf(patch) });
|
|
1538
|
+
return (Array.isArray(created) ? created[0] : created).id;
|
|
1539
|
+
};
|
|
1540
|
+
return { updateRecord, addRecord };
|
|
1541
|
+
}
|
|
1542
|
+
function buildResult(state, mapped, connection, mapBack, resolveMappedColumnId, writes) {
|
|
1493
1543
|
const { runtime, api } = connection;
|
|
1494
1544
|
const status = deriveStatus(state);
|
|
1495
1545
|
const columns = runtime.options.columns;
|
|
@@ -1510,6 +1560,8 @@ function buildResult(state, mapped, connection, mapBack, resolveMappedColumnId)
|
|
|
1510
1560
|
mode: state.isNewRecord ? "new-row" : state.record ? "row" : "empty",
|
|
1511
1561
|
resolveMappedColumnId,
|
|
1512
1562
|
mapBack,
|
|
1563
|
+
updateRecord: writes.updateRecord,
|
|
1564
|
+
addRecord: writes.addRecord,
|
|
1513
1565
|
currentTableId: state.currentTableId,
|
|
1514
1566
|
actionStatus: state.actionStatus,
|
|
1515
1567
|
actionError: state.actionError,
|
|
@@ -1532,7 +1584,7 @@ function useGristResult(connection) {
|
|
|
1532
1584
|
);
|
|
1533
1585
|
const mapBack = (0, import_react.useCallback)(
|
|
1534
1586
|
(patch) => {
|
|
1535
|
-
const { fields, skipped } = mapBackPatch(patch, mappings);
|
|
1587
|
+
const { fields, skipped } = mapBackPatch(patch, mappings, runtime.options.columns);
|
|
1536
1588
|
const previous = runtime.getState().mapBackSkipped;
|
|
1537
1589
|
if (previous.join("\0") !== skipped.join("\0")) runtime.update({ mapBackSkipped: skipped });
|
|
1538
1590
|
return fields;
|
|
@@ -1540,9 +1592,10 @@ function useGristResult(connection) {
|
|
|
1540
1592
|
[mappings, runtime]
|
|
1541
1593
|
);
|
|
1542
1594
|
const mapped = useMapped(state, runtime.options);
|
|
1595
|
+
const writes = (0, import_react.useMemo)(() => recordWrites(connection), [connection]);
|
|
1543
1596
|
return (0, import_react.useMemo)(
|
|
1544
|
-
() => buildResult(state, mapped, connection, mapBack, resolveMappedColumnId),
|
|
1545
|
-
[state, mapped, connection, mapBack, resolveMappedColumnId]
|
|
1597
|
+
() => buildResult(state, mapped, connection, mapBack, resolveMappedColumnId, writes),
|
|
1598
|
+
[state, mapped, connection, mapBack, resolveMappedColumnId, writes]
|
|
1546
1599
|
);
|
|
1547
1600
|
}
|
|
1548
1601
|
function useGrist(options) {
|
|
@@ -1578,6 +1631,17 @@ var DETACHED = (() => {
|
|
|
1578
1631
|
return { runtime, api: createGristApi(runtime) };
|
|
1579
1632
|
})();
|
|
1580
1633
|
|
|
1634
|
+
// src/sdk/define-widget.ts
|
|
1635
|
+
function defineGristWidget(definition) {
|
|
1636
|
+
const { title, description, ...options } = definition;
|
|
1637
|
+
return {
|
|
1638
|
+
definition,
|
|
1639
|
+
options,
|
|
1640
|
+
metadata: { title, description },
|
|
1641
|
+
useGrist: () => useGrist(options)
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1581
1645
|
// src/sdk/components/grist-widget-provider.tsx
|
|
1582
1646
|
var import_react2 = require("react");
|
|
1583
1647
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -1805,9 +1869,9 @@ function useGristSchema(options) {
|
|
|
1805
1869
|
const [document2, setDocument] = (0, import_react4.useState)(null);
|
|
1806
1870
|
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
1807
1871
|
const [error, setError] = (0, import_react4.useState)(null);
|
|
1808
|
-
const includeRowsForBuild = replicaRowMode === "schema-only" ? false : replicaRowMode === "schema+data" ? "full" : "samples";
|
|
1809
1872
|
const load = (0, import_react4.useCallback)(async () => {
|
|
1810
1873
|
if (!docApi) return;
|
|
1874
|
+
const includeRowsForBuild = replicaRowMode === "schema-only" ? false : replicaRowMode === "schema+data" ? "full" : "samples";
|
|
1811
1875
|
setLoading(true);
|
|
1812
1876
|
setError(null);
|
|
1813
1877
|
try {
|
|
@@ -2001,6 +2065,8 @@ function gristAddTableAction(tableId, tableInfo) {
|
|
|
2001
2065
|
GristWidgetProvider,
|
|
2002
2066
|
checkGristCell,
|
|
2003
2067
|
decodeGristValue,
|
|
2068
|
+
defineGristWidget,
|
|
2069
|
+
encodeGristCell,
|
|
2004
2070
|
encodeGristValue,
|
|
2005
2071
|
extractGristAttachmentId,
|
|
2006
2072
|
extractGristAttachmentIdsFromCell,
|