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
|
@@ -20,25 +20,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/emulator/testing/index.ts
|
|
21
21
|
var testing_exports = {};
|
|
22
22
|
__export(testing_exports, {
|
|
23
|
-
act: () =>
|
|
23
|
+
act: () => import_react13.act,
|
|
24
24
|
actionsOf: () => actionsOf,
|
|
25
|
-
cleanup: () =>
|
|
25
|
+
cleanup: () => import_react13.cleanup,
|
|
26
26
|
eventsOf: () => eventsOf,
|
|
27
|
-
fireEvent: () =>
|
|
27
|
+
fireEvent: () => import_react13.fireEvent,
|
|
28
28
|
installJsdomStubs: () => installJsdomStubs,
|
|
29
29
|
presets: () => presets,
|
|
30
|
-
render: () =>
|
|
30
|
+
render: () => import_react13.render,
|
|
31
31
|
renderWithGrist: () => renderWithGrist,
|
|
32
|
-
screen: () =>
|
|
32
|
+
screen: () => import_react13.screen,
|
|
33
33
|
smokeTestWidget: () => smokeTestWidget,
|
|
34
|
-
waitFor: () =>
|
|
34
|
+
waitFor: () => import_react13.waitFor,
|
|
35
35
|
waitForEvent: () => waitForEvent,
|
|
36
|
-
within: () =>
|
|
36
|
+
within: () => import_react13.within
|
|
37
37
|
});
|
|
38
38
|
module.exports = __toCommonJS(testing_exports);
|
|
39
39
|
|
|
40
40
|
// src/emulator/testing/render-with-grist.tsx
|
|
41
|
-
var
|
|
41
|
+
var import_react10 = require("@testing-library/react");
|
|
42
|
+
|
|
43
|
+
// src/app/mount-grist-widget.tsx
|
|
44
|
+
var import_react9 = require("react");
|
|
45
|
+
var import_client = require("react-dom/client");
|
|
42
46
|
|
|
43
47
|
// src/sdk/use-grist.ts
|
|
44
48
|
var import_react = require("react");
|
|
@@ -586,16 +590,24 @@ function createGristApi(runtime) {
|
|
|
586
590
|
throw err;
|
|
587
591
|
}
|
|
588
592
|
};
|
|
589
|
-
|
|
593
|
+
let running = 0;
|
|
594
|
+
let failure = null;
|
|
595
|
+
const write = (fn) => {
|
|
596
|
+
if (running++ === 0) failure = null;
|
|
590
597
|
runtime.update({ actionStatus: "running", actionError: null });
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
598
|
+
const done = (async () => {
|
|
599
|
+
try {
|
|
600
|
+
return await fn();
|
|
601
|
+
} catch (err) {
|
|
602
|
+
failure = message(err);
|
|
603
|
+
throw err;
|
|
604
|
+
} finally {
|
|
605
|
+
if (--running === 0) runtime.update(failure ? { actionStatus: "error", actionError: failure } : { actionStatus: "idle" });
|
|
606
|
+
}
|
|
607
|
+
})();
|
|
608
|
+
done.catch(() => {
|
|
609
|
+
});
|
|
610
|
+
return done;
|
|
599
611
|
};
|
|
600
612
|
const tableOps = (resolve) => ({
|
|
601
613
|
getTableId: () => resolve().getTableId(),
|
|
@@ -700,38 +712,6 @@ function validateColumnMappings(spec, mappings) {
|
|
|
700
712
|
};
|
|
701
713
|
}
|
|
702
714
|
|
|
703
|
-
// src/sdk/runtime/derive.ts
|
|
704
|
-
var ACCESS_RANK = { none: 0, "read table": 1, full: 2 };
|
|
705
|
-
function parseAccess(raw) {
|
|
706
|
-
return raw === "none" || raw === "read table" || raw === "full" ? raw : null;
|
|
707
|
-
}
|
|
708
|
-
function accessLabel(level) {
|
|
709
|
-
return level === "full" ? "Full document access" : level === "read table" ? "Read selected table" : "No document access";
|
|
710
|
-
}
|
|
711
|
-
function accessDenied(state) {
|
|
712
|
-
return state.grantedAccess !== null && ACCESS_RANK[state.grantedAccess] < ACCESS_RANK[state.declaredAccess];
|
|
713
|
-
}
|
|
714
|
-
function deriveStatus(state) {
|
|
715
|
-
switch (state.connection) {
|
|
716
|
-
case "connecting":
|
|
717
|
-
return "booting";
|
|
718
|
-
case "not-embedded":
|
|
719
|
-
case "unavailable":
|
|
720
|
-
return "unavailable";
|
|
721
|
-
case "error":
|
|
722
|
-
return "error";
|
|
723
|
-
case "ready":
|
|
724
|
-
return accessDenied(state) ? "error" : "ready";
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
var MAPPINGS_OK = Object.freeze({ ok: true, pending: false, missing: [], emptyMultiples: [] });
|
|
728
|
-
var MAPPINGS_PENDING = Object.freeze({ ok: true, pending: true, missing: [], emptyMultiples: [] });
|
|
729
|
-
function deriveColumnMappingStatus(columns, mappings) {
|
|
730
|
-
if (!columns?.length) return MAPPINGS_OK;
|
|
731
|
-
if (mappings === null) return MAPPINGS_PENDING;
|
|
732
|
-
return validateColumnMappings(columns, mappings);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
715
|
// src/sdk/runtime/cells.ts
|
|
736
716
|
var ok = (value) => ({ ok: true, value });
|
|
737
717
|
var bad = (message2) => ({ ok: false, message: message2 });
|
|
@@ -795,10 +775,13 @@ function checkOne(base, value) {
|
|
|
795
775
|
return ok(value);
|
|
796
776
|
}
|
|
797
777
|
}
|
|
778
|
+
function basesOf(type) {
|
|
779
|
+
return (type ?? "Any").split(",").map((t) => t.trim().split(":")[0]).filter(Boolean);
|
|
780
|
+
}
|
|
798
781
|
function checkGristCell(type, value) {
|
|
799
782
|
const error = formulaError(value);
|
|
800
783
|
if (error) return bad(`formula error (${error})`);
|
|
801
|
-
const bases = (type
|
|
784
|
+
const bases = basesOf(type);
|
|
802
785
|
if (bases.length === 0 || bases.includes("Any")) return ok(value === void 0 ? null : value);
|
|
803
786
|
let first = null;
|
|
804
787
|
for (const base of bases) {
|
|
@@ -808,6 +791,19 @@ function checkGristCell(type, value) {
|
|
|
808
791
|
}
|
|
809
792
|
return first;
|
|
810
793
|
}
|
|
794
|
+
var LIST_BASES = ["ChoiceList", "RefList", "Attachments"];
|
|
795
|
+
function encodeGristCell(type, value) {
|
|
796
|
+
if (value === null || value === void 0) return value;
|
|
797
|
+
const bases = basesOf(type);
|
|
798
|
+
const accepts = (...names) => names.some((n) => bases.includes(n));
|
|
799
|
+
if (value instanceof Date) return accepts("Date", "DateTime") ? Math.floor(value.getTime() / 1e3) : value;
|
|
800
|
+
if (Array.isArray(value)) {
|
|
801
|
+
if (!accepts(...LIST_BASES) || value[0] === "L") return value;
|
|
802
|
+
return ["L", ...value.map((item) => rowIdOf(item) ?? item)];
|
|
803
|
+
}
|
|
804
|
+
if (accepts("Ref") && typeof value === "object") return rowIdOf(value) ?? value;
|
|
805
|
+
return value;
|
|
806
|
+
}
|
|
811
807
|
var nameOf = (c) => typeof c === "string" ? c : c.name;
|
|
812
808
|
var typeOf = (c) => typeof c === "string" ? void 0 : c.type;
|
|
813
809
|
var isOptional = (c) => typeof c === "object" && c.optional === true;
|
|
@@ -848,6 +844,38 @@ function mapAndCheck(record, columns, mappings, schema) {
|
|
|
848
844
|
return { value: out, issues };
|
|
849
845
|
}
|
|
850
846
|
|
|
847
|
+
// src/sdk/runtime/derive.ts
|
|
848
|
+
var ACCESS_RANK = { none: 0, "read table": 1, full: 2 };
|
|
849
|
+
function parseAccess(raw) {
|
|
850
|
+
return raw === "none" || raw === "read table" || raw === "full" ? raw : null;
|
|
851
|
+
}
|
|
852
|
+
function accessLabel(level) {
|
|
853
|
+
return level === "full" ? "Full document access" : level === "read table" ? "Read selected table" : "No document access";
|
|
854
|
+
}
|
|
855
|
+
function accessDenied(state) {
|
|
856
|
+
return state.grantedAccess !== null && ACCESS_RANK[state.grantedAccess] < ACCESS_RANK[state.declaredAccess];
|
|
857
|
+
}
|
|
858
|
+
function deriveStatus(state) {
|
|
859
|
+
switch (state.connection) {
|
|
860
|
+
case "connecting":
|
|
861
|
+
return "booting";
|
|
862
|
+
case "not-embedded":
|
|
863
|
+
case "unavailable":
|
|
864
|
+
return "unavailable";
|
|
865
|
+
case "error":
|
|
866
|
+
return "error";
|
|
867
|
+
case "ready":
|
|
868
|
+
return accessDenied(state) ? "error" : "ready";
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
var MAPPINGS_OK = Object.freeze({ ok: true, pending: false, missing: [], emptyMultiples: [] });
|
|
872
|
+
var MAPPINGS_PENDING = Object.freeze({ ok: true, pending: true, missing: [], emptyMultiples: [] });
|
|
873
|
+
function deriveColumnMappingStatus(columns, mappings) {
|
|
874
|
+
if (!columns?.length) return MAPPINGS_OK;
|
|
875
|
+
if (mappings === null) return MAPPINGS_PENDING;
|
|
876
|
+
return validateColumnMappings(columns, mappings);
|
|
877
|
+
}
|
|
878
|
+
|
|
851
879
|
// src/sdk/helpers/grist-widget-interaction.ts
|
|
852
880
|
function parseGristWidgetLinkingInfo(raw) {
|
|
853
881
|
if (!raw || typeof raw !== "object" || !("asTarget" in raw)) return void 0;
|
|
@@ -1227,110 +1255,167 @@ function useNearestGristState() {
|
|
|
1227
1255
|
return { connection, state, cellIssues: issues };
|
|
1228
1256
|
}
|
|
1229
1257
|
|
|
1230
|
-
// src/
|
|
1231
|
-
function
|
|
1232
|
-
return
|
|
1233
|
-
async run(name) {
|
|
1234
|
-
bus.emit({ type: "user-action", action: ["RunCommand", name] });
|
|
1235
|
-
}
|
|
1236
|
-
};
|
|
1258
|
+
// src/sdk/define-widget.ts
|
|
1259
|
+
function isGristWidget(value) {
|
|
1260
|
+
return typeof value === "object" && value !== null && "definition" in value && "useGrist" in value;
|
|
1237
1261
|
}
|
|
1238
1262
|
|
|
1239
|
-
// src/
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
return
|
|
1263
|
+
// src/sdk/components/grist-widget-provider.tsx
|
|
1264
|
+
var import_react2 = require("react");
|
|
1265
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1266
|
+
function GristWidgetProvider({ options, children }) {
|
|
1267
|
+
const [connection] = (0, import_react2.useState)(() => newConnection(options));
|
|
1268
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GristContextProvider, { value: connection, children });
|
|
1245
1269
|
}
|
|
1246
1270
|
|
|
1247
|
-
// src/
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1271
|
+
// src/sdk/components/grist-boundary.tsx
|
|
1272
|
+
var import_react3 = require("react");
|
|
1273
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1274
|
+
var shellStyle = {
|
|
1275
|
+
alignItems: "center",
|
|
1276
|
+
background: "var(--background)",
|
|
1277
|
+
boxSizing: "border-box",
|
|
1278
|
+
color: "var(--foreground)",
|
|
1279
|
+
display: "flex",
|
|
1280
|
+
flexDirection: "column",
|
|
1281
|
+
justifyContent: "center",
|
|
1282
|
+
minHeight: "100%",
|
|
1283
|
+
padding: "clamp(16px, 5vh, 32px)",
|
|
1284
|
+
width: "100%"
|
|
1285
|
+
};
|
|
1286
|
+
var cardStyle = {
|
|
1287
|
+
background: "var(--card)",
|
|
1288
|
+
border: "1px solid var(--border)",
|
|
1289
|
+
borderRadius: 12,
|
|
1290
|
+
boxShadow: "0 1px 3px rgb(0 0 0 / 0.12)",
|
|
1291
|
+
maxWidth: 420,
|
|
1292
|
+
padding: "20px 22px",
|
|
1293
|
+
textAlign: "center",
|
|
1294
|
+
width: "100%"
|
|
1295
|
+
};
|
|
1296
|
+
var titleStyle = {
|
|
1297
|
+
color: "var(--foreground)",
|
|
1298
|
+
fontSize: 15,
|
|
1299
|
+
fontWeight: 600,
|
|
1300
|
+
lineHeight: 1.35,
|
|
1301
|
+
margin: 0
|
|
1302
|
+
};
|
|
1303
|
+
var messageStyle = {
|
|
1304
|
+
color: "var(--muted-foreground)",
|
|
1305
|
+
fontSize: 14,
|
|
1306
|
+
lineHeight: 1.5,
|
|
1307
|
+
margin: "10px 0 0"
|
|
1308
|
+
};
|
|
1309
|
+
var buttonStyle = {
|
|
1310
|
+
background: "var(--background)",
|
|
1311
|
+
border: "1px solid var(--border)",
|
|
1312
|
+
borderRadius: 8,
|
|
1313
|
+
color: "var(--foreground)",
|
|
1314
|
+
cursor: "pointer",
|
|
1315
|
+
fontSize: 13,
|
|
1316
|
+
fontWeight: 500,
|
|
1317
|
+
marginTop: 16,
|
|
1318
|
+
padding: "8px 14px"
|
|
1319
|
+
};
|
|
1320
|
+
function GristBoundaryScreen({
|
|
1321
|
+
title,
|
|
1322
|
+
message: message2,
|
|
1323
|
+
tone = "default",
|
|
1324
|
+
showRetry = false,
|
|
1325
|
+
actions,
|
|
1326
|
+
retryLabel = "Try reconnecting",
|
|
1327
|
+
retryBusyLabel = "Reconnecting\u2026"
|
|
1328
|
+
}) {
|
|
1329
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: shellStyle, role: tone === "error" ? "alert" : "status", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardStyle, children: [
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: titleStyle, children: title }),
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: messageStyle, children: message2 }),
|
|
1332
|
+
showRetry && actions ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1333
|
+
"button",
|
|
1334
|
+
{
|
|
1335
|
+
disabled: actions.isReloading,
|
|
1336
|
+
onClick: () => void actions.reload(),
|
|
1337
|
+
style: {
|
|
1338
|
+
...buttonStyle,
|
|
1339
|
+
cursor: actions.isReloading ? "not-allowed" : "pointer",
|
|
1340
|
+
opacity: actions.isReloading ? 0.7 : 1
|
|
1341
|
+
},
|
|
1342
|
+
type: "button",
|
|
1343
|
+
children: actions.isReloading ? retryBusyLabel : retryLabel
|
|
1344
|
+
}
|
|
1345
|
+
) : null
|
|
1346
|
+
] }) });
|
|
1347
|
+
}
|
|
1348
|
+
function boundaryScreen(state, columns) {
|
|
1349
|
+
switch (state.connection) {
|
|
1350
|
+
case "connecting":
|
|
1351
|
+
return { title: "Connecting to Grist", message: "Asking Grist for the access this widget needs\u2026" };
|
|
1352
|
+
case "not-embedded":
|
|
1353
|
+
return {
|
|
1354
|
+
title: "Open inside Grist",
|
|
1355
|
+
message: "Open this page as a custom widget inside a Grist document (it runs in an embedded panel, not on its own).",
|
|
1356
|
+
retry: true
|
|
1256
1357
|
};
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1358
|
+
case "unavailable":
|
|
1359
|
+
return {
|
|
1360
|
+
title: "Open inside Grist",
|
|
1361
|
+
message: "The Grist plugin did not load. Reload the page, or open this URL as a custom widget inside a Grist document.",
|
|
1362
|
+
retry: true
|
|
1363
|
+
};
|
|
1364
|
+
case "error":
|
|
1365
|
+
return {
|
|
1366
|
+
title: "Something went wrong",
|
|
1367
|
+
message: state.connectionError ?? "Something went wrong connecting to Grist. Try again, or reload the page.",
|
|
1368
|
+
tone: "error",
|
|
1369
|
+
retry: true
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
if (accessDenied(state)) {
|
|
1373
|
+
return {
|
|
1374
|
+
title: "More access needed",
|
|
1375
|
+
message: `Set Access level to at least "${accessLabel(state.declaredAccess)}" in the Custom widget settings.`,
|
|
1376
|
+
tone: "error",
|
|
1377
|
+
retry: true
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
const mapping = deriveColumnMappingStatus(columns, state.mappings);
|
|
1381
|
+
if (mapping.pending) return { title: "Connecting to Grist", message: "Waiting for Grist to report the column mapping\u2026" };
|
|
1382
|
+
if (!mapping.ok) {
|
|
1383
|
+
return {
|
|
1384
|
+
title: "Map your columns",
|
|
1385
|
+
message: "Open the widget settings in Grist (Creator Panel \u2192 Custom widget) and connect each required column to your table."
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
return null;
|
|
1265
1389
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1390
|
+
function GristBoundary({ children }) {
|
|
1391
|
+
const connection = useGristRuntime();
|
|
1392
|
+
if (!connection) throw new Error("<GristBoundary> must be used inside <GristWidgetProvider> (mountGristWidget renders both).");
|
|
1393
|
+
const state = useGristState(connection);
|
|
1394
|
+
const [isReloading, setIsReloading] = (0, import_react3.useState)(false);
|
|
1395
|
+
const reload = (0, import_react3.useCallback)(async () => {
|
|
1396
|
+
setIsReloading(true);
|
|
1397
|
+
try {
|
|
1398
|
+
await connection.runtime.reload();
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
console.error("[grist-widget-sdk] reload failed:", err);
|
|
1401
|
+
} finally {
|
|
1402
|
+
setIsReloading(false);
|
|
1272
1403
|
}
|
|
1273
|
-
};
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
colRefToColId.set(col.colRef, colId);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
for (const [colId, col] of Object.entries(columns)) {
|
|
1290
|
-
const type = col?.type;
|
|
1291
|
-
if (typeof type !== "string" || !type.startsWith("Ref:") && !type.startsWith("RefList:")) {
|
|
1292
|
-
continue;
|
|
1293
|
-
}
|
|
1294
|
-
if (typeof col.displayColRef === "number" && Number.isFinite(col.displayColRef)) {
|
|
1295
|
-
const helperColId = colRefToColId.get(col.displayColRef);
|
|
1296
|
-
if (helperColId) {
|
|
1297
|
-
out[colId] = helperColId;
|
|
1298
|
-
usedHelpers.add(helperColId);
|
|
1299
|
-
continue;
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
const match = helperColumns.find(([, helper]) => {
|
|
1303
|
-
const formula = helper?.formula;
|
|
1304
|
-
return typeof formula === "string" && (formula.includes(`$${colId}.`) || formula.includes(`$${colId}`));
|
|
1305
|
-
});
|
|
1306
|
-
if (match) {
|
|
1307
|
-
out[colId] = match[0];
|
|
1308
|
-
usedHelpers.add(match[0]);
|
|
1404
|
+
}, [connection]);
|
|
1405
|
+
const screen2 = boundaryScreen(state, connection.runtime.options.columns);
|
|
1406
|
+
if (!screen2) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
1407
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1408
|
+
GristBoundaryScreen,
|
|
1409
|
+
{
|
|
1410
|
+
title: screen2.title,
|
|
1411
|
+
message: screen2.message,
|
|
1412
|
+
tone: screen2.tone,
|
|
1413
|
+
showRetry: screen2.retry,
|
|
1414
|
+
actions: { reload, isReloading },
|
|
1415
|
+
retryLabel: screen2.tone === "error" ? "Try again" : "Try reconnecting",
|
|
1416
|
+
retryBusyLabel: screen2.tone === "error" ? "Retrying\u2026" : "Reconnecting\u2026"
|
|
1309
1417
|
}
|
|
1310
|
-
|
|
1311
|
-
const unresolvedRefs = Object.entries(columns).filter(([colId, col]) => {
|
|
1312
|
-
const type = col?.type;
|
|
1313
|
-
return !out[colId] && typeof type === "string" && (type.startsWith("Ref:") || type.startsWith("RefList:"));
|
|
1314
|
-
}).sort((a, b) => (a[1]?.parentPos ?? Number.MAX_SAFE_INTEGER) - (b[1]?.parentPos ?? Number.MAX_SAFE_INTEGER));
|
|
1315
|
-
const helperByPos = helperColumns.filter(([helperId]) => !usedHelpers.has(helperId)).sort((a, b) => (a[1]?.parentPos ?? Number.MAX_SAFE_INTEGER) - (b[1]?.parentPos ?? Number.MAX_SAFE_INTEGER));
|
|
1316
|
-
for (const [refId, refCol] of unresolvedRefs) {
|
|
1317
|
-
const refPos = refCol?.parentPos ?? Number.MAX_SAFE_INTEGER;
|
|
1318
|
-
const candidate = helperByPos.find(([, helperCol]) => {
|
|
1319
|
-
const helperPos = helperCol?.parentPos ?? Number.MAX_SAFE_INTEGER;
|
|
1320
|
-
return helperPos > refPos;
|
|
1321
|
-
});
|
|
1322
|
-
if (!candidate) continue;
|
|
1323
|
-
out[refId] = candidate[0];
|
|
1324
|
-
usedHelpers.add(candidate[0]);
|
|
1325
|
-
}
|
|
1326
|
-
for (const [refId] of unresolvedRefs) {
|
|
1327
|
-
if (out[refId]) continue;
|
|
1328
|
-
const candidate = helperByPos.find(([helperId]) => !usedHelpers.has(helperId));
|
|
1329
|
-
if (!candidate) continue;
|
|
1330
|
-
out[refId] = candidate[0];
|
|
1331
|
-
usedHelpers.add(candidate[0]);
|
|
1332
|
-
}
|
|
1333
|
-
return out;
|
|
1418
|
+
);
|
|
1334
1419
|
}
|
|
1335
1420
|
|
|
1336
1421
|
// src/sdk/helpers/replica-normalize.ts
|
|
@@ -1346,2213 +1431,2316 @@ function normalizeReplicaDocumentInput(doc) {
|
|
|
1346
1431
|
};
|
|
1347
1432
|
}
|
|
1348
1433
|
|
|
1349
|
-
// src/
|
|
1350
|
-
var
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
if (pa !== void 0 && pb !== void 0 && pa !== pb) return pa - pb;
|
|
1377
|
-
if (pa !== void 0 && pb === void 0) return -1;
|
|
1378
|
-
if (pa === void 0 && pb !== void 0) return 1;
|
|
1379
|
-
return ka.localeCompare(kb);
|
|
1380
|
-
});
|
|
1381
|
-
return Object.fromEntries(entries);
|
|
1382
|
-
}
|
|
1383
|
-
function inferColumnsFromRows(rows) {
|
|
1384
|
-
const columns = {};
|
|
1385
|
-
for (const row of rows) {
|
|
1386
|
-
for (const key of Object.keys(row)) {
|
|
1387
|
-
if (key === SYSTEM_COLUMN) continue;
|
|
1388
|
-
if (!columns[key]) columns[key] = inferColumnType(row[key]);
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
return columns;
|
|
1392
|
-
}
|
|
1393
|
-
function inferColumnType(sample) {
|
|
1394
|
-
switch (typeof sample) {
|
|
1395
|
-
case "number":
|
|
1396
|
-
return { type: "Numeric" };
|
|
1397
|
-
case "boolean":
|
|
1398
|
-
return { type: "Bool" };
|
|
1399
|
-
case "string":
|
|
1400
|
-
return { type: "Text" };
|
|
1401
|
-
default:
|
|
1402
|
-
return { type: "Any" };
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
function buildTable(tableId, input) {
|
|
1406
|
-
const rows = input.rows ?? [];
|
|
1407
|
-
const inferred = inferColumnsFromRows(rows);
|
|
1408
|
-
const merged = {};
|
|
1409
|
-
for (const [colId, col] of Object.entries({ ...inferred, ...input.columns ?? {} })) {
|
|
1410
|
-
merged[colId] = enrichEmulatorColumnMetadata(col);
|
|
1411
|
-
}
|
|
1412
|
-
const columns = sortEmulatorColumnsByParentPos(merged);
|
|
1413
|
-
return {
|
|
1414
|
-
tableId,
|
|
1415
|
-
label: input.label,
|
|
1416
|
-
columns,
|
|
1417
|
-
rows: rows.map((row) => ({ ...row }))
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
function createDocument(input) {
|
|
1421
|
-
if (!input.tables || Object.keys(input.tables).length === 0) {
|
|
1422
|
-
throw new Error("createDocument: at least one table is required");
|
|
1423
|
-
}
|
|
1424
|
-
const normalized = normalizeReplicaDocumentInput(input);
|
|
1425
|
-
const tables = /* @__PURE__ */ new Map();
|
|
1426
|
-
for (const [tableId, tableInput] of Object.entries(normalized.tables)) {
|
|
1427
|
-
tables.set(tableId, buildTable(tableId, tableInput));
|
|
1434
|
+
// src/sdk/use-widget-metadata.ts
|
|
1435
|
+
var import_react4 = require("react");
|
|
1436
|
+
function useWidgetMetadata(input) {
|
|
1437
|
+
(0, import_react4.useEffect)(() => {
|
|
1438
|
+
if (!input) return;
|
|
1439
|
+
const { title, description } = input;
|
|
1440
|
+
const canonicalUrl = window.location.href;
|
|
1441
|
+
document.title = title;
|
|
1442
|
+
setMetaTag("name", "title", title);
|
|
1443
|
+
setMetaTag("name", "description", description);
|
|
1444
|
+
setMetaTag("property", "og:title", title);
|
|
1445
|
+
setMetaTag("property", "og:description", description);
|
|
1446
|
+
setMetaTag("property", "og:type", "website");
|
|
1447
|
+
setMetaTag("property", "og:url", canonicalUrl);
|
|
1448
|
+
setMetaTag("name", "twitter:card", "summary");
|
|
1449
|
+
setMetaTag("name", "twitter:title", title);
|
|
1450
|
+
setMetaTag("name", "twitter:description", description);
|
|
1451
|
+
}, [input]);
|
|
1452
|
+
}
|
|
1453
|
+
function setMetaTag(key, value, content) {
|
|
1454
|
+
let meta = document.head.querySelector(
|
|
1455
|
+
`meta[${key}="${value}"]`
|
|
1456
|
+
);
|
|
1457
|
+
if (!meta) {
|
|
1458
|
+
meta = document.createElement("meta");
|
|
1459
|
+
meta.setAttribute(key, value);
|
|
1460
|
+
document.head.appendChild(meta);
|
|
1428
1461
|
}
|
|
1429
|
-
|
|
1430
|
-
const requestedTableId = normalized.selection?.tableId;
|
|
1431
|
-
const selectedTableId = requestedTableId && tables.has(requestedTableId) ? requestedTableId : firstTableId;
|
|
1432
|
-
const selectedTable = tables.get(selectedTableId);
|
|
1433
|
-
const fallbackRowId = typeof selectedTable.rows[0]?.id === "number" ? selectedTable.rows[0].id : null;
|
|
1434
|
-
const selection = {
|
|
1435
|
-
tableId: selectedTableId,
|
|
1436
|
-
rowId: normalized.selection?.rowId ?? fallbackRowId,
|
|
1437
|
-
mappings: normalized.selection?.mappings,
|
|
1438
|
-
cursor: normalized.selection?.cursor
|
|
1439
|
-
};
|
|
1440
|
-
return {
|
|
1441
|
-
docName: normalized.docName ?? "Untitled emulator document",
|
|
1442
|
-
tables,
|
|
1443
|
-
selection,
|
|
1444
|
-
widgetOptions: { ...normalized.widgetOptions ?? {} },
|
|
1445
|
-
widgetInteraction: { ...normalized.widgetInteraction ?? {} },
|
|
1446
|
-
configuration: normalized.configuration ?? null,
|
|
1447
|
-
accessToken: normalized.accessToken ?? null
|
|
1448
|
-
};
|
|
1449
|
-
}
|
|
1450
|
-
function getTable(state, tableId) {
|
|
1451
|
-
const table = state.tables.get(tableId);
|
|
1452
|
-
if (!table) throw new Error(`Unknown table: ${tableId}`);
|
|
1453
|
-
return table;
|
|
1454
|
-
}
|
|
1455
|
-
function getSelectedTable(state) {
|
|
1456
|
-
return getTable(state, state.selection.tableId);
|
|
1462
|
+
meta.setAttribute("content", content);
|
|
1457
1463
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
}
|
|
1463
|
-
return max + 1;
|
|
1464
|
+
|
|
1465
|
+
// src/sdk/helpers/grist-user-actions.ts
|
|
1466
|
+
function gristUpdateRecordAction(tableId, rowId, patch) {
|
|
1467
|
+
return ["UpdateRecord", tableId, rowId, patch];
|
|
1464
1468
|
}
|
|
1465
|
-
function
|
|
1466
|
-
return
|
|
1469
|
+
function gristAddRecordAction(tableId, values) {
|
|
1470
|
+
return ["AddRecord", tableId, null, values];
|
|
1467
1471
|
}
|
|
1468
|
-
function
|
|
1469
|
-
return
|
|
1472
|
+
function gristRemoveRecordAction(tableId, rowId) {
|
|
1473
|
+
return ["RemoveRecord", tableId, rowId];
|
|
1470
1474
|
}
|
|
1471
|
-
function
|
|
1472
|
-
|
|
1473
|
-
if (col.formulaFn) {
|
|
1474
|
-
try {
|
|
1475
|
-
row[colId] = col.formulaFn(row, { table, document: state });
|
|
1476
|
-
} catch (err) {
|
|
1477
|
-
row[colId] = formulaError2(err);
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1475
|
+
function gristBulkUpdateRecordAction(tableId, rowIds, columnarPatch) {
|
|
1476
|
+
return ["BulkUpdateRecord", tableId, rowIds, columnarPatch];
|
|
1481
1477
|
}
|
|
1482
|
-
function
|
|
1483
|
-
|
|
1478
|
+
function gristBulkAddRecordAction(tableId, values, rowIds) {
|
|
1479
|
+
const length = inferBulkLength(values);
|
|
1480
|
+
const ids = rowIds ?? new Array(length).fill(null);
|
|
1481
|
+
return ["BulkAddRecord", tableId, ids, values];
|
|
1484
1482
|
}
|
|
1485
|
-
function
|
|
1486
|
-
|
|
1487
|
-
if (!Array.isArray(raw)) return [];
|
|
1488
|
-
const items = raw[0] === "L" ? raw.slice(1) : raw;
|
|
1489
|
-
const out = [];
|
|
1490
|
-
for (const item of items) {
|
|
1491
|
-
if (typeof item === "number" && Number.isFinite(item) && item > 0) {
|
|
1492
|
-
out.push(Math.trunc(item));
|
|
1493
|
-
continue;
|
|
1494
|
-
}
|
|
1495
|
-
if (item && typeof item === "object" && "rowId" in item) {
|
|
1496
|
-
const r = item.rowId;
|
|
1497
|
-
if (typeof r === "number" && Number.isFinite(r) && r > 0) {
|
|
1498
|
-
out.push(Math.trunc(r));
|
|
1499
|
-
continue;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
const t = coerceGristDocApiRefRowId(item);
|
|
1503
|
-
if (t !== void 0 && t > 0) out.push(Math.trunc(t));
|
|
1504
|
-
}
|
|
1505
|
-
return Array.from(new Set(out));
|
|
1483
|
+
function gristBulkRemoveRecordAction(tableId, rowIds) {
|
|
1484
|
+
return ["BulkRemoveRecord", tableId, rowIds];
|
|
1506
1485
|
}
|
|
1507
|
-
function
|
|
1508
|
-
if (
|
|
1509
|
-
|
|
1510
|
-
const m = formula.match(re);
|
|
1511
|
-
return m?.[1] ?? null;
|
|
1486
|
+
function inferBulkLength(values) {
|
|
1487
|
+
for (const arr of Object.values(values)) if (Array.isArray(arr)) return arr.length;
|
|
1488
|
+
return 0;
|
|
1512
1489
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1490
|
+
|
|
1491
|
+
// src/app/chrome/local-dev-page.tsx
|
|
1492
|
+
var import_react5 = require("react");
|
|
1493
|
+
|
|
1494
|
+
// src/app/chrome/tokens.ts
|
|
1495
|
+
var token = (name, fallback) => `var(--${name}, ${fallback})`;
|
|
1496
|
+
var tokens = {
|
|
1497
|
+
background: token("background", "#ffffff"),
|
|
1498
|
+
foreground: token("foreground", "#0a0a0a"),
|
|
1499
|
+
card: token("card", "#ffffff"),
|
|
1500
|
+
mutedForeground: token("muted-foreground", "#737373"),
|
|
1501
|
+
border: token("border", "#e5e5e5"),
|
|
1502
|
+
primary: token("primary", "#171717"),
|
|
1503
|
+
primaryForeground: token("primary-foreground", "#fafafa"),
|
|
1504
|
+
destructive: token("destructive", "#dc2626"),
|
|
1505
|
+
radius: token("radius", "0.625rem")
|
|
1506
|
+
};
|
|
1507
|
+
var pageStyle = {
|
|
1508
|
+
boxSizing: "border-box",
|
|
1509
|
+
display: "flex",
|
|
1510
|
+
flexDirection: "column",
|
|
1511
|
+
justifyContent: "center",
|
|
1512
|
+
maxWidth: "28rem",
|
|
1513
|
+
minHeight: "100%",
|
|
1514
|
+
margin: "0 auto",
|
|
1515
|
+
padding: "4rem 1.5rem",
|
|
1516
|
+
color: tokens.foreground,
|
|
1517
|
+
font: "inherit"
|
|
1518
|
+
};
|
|
1519
|
+
var eyebrowStyle = {
|
|
1520
|
+
margin: "0 0 0.5rem",
|
|
1521
|
+
textAlign: "center",
|
|
1522
|
+
fontSize: "0.875rem",
|
|
1523
|
+
fontWeight: 500,
|
|
1524
|
+
color: tokens.mutedForeground
|
|
1525
|
+
};
|
|
1526
|
+
var headingStyle = {
|
|
1527
|
+
margin: 0,
|
|
1528
|
+
textAlign: "center",
|
|
1529
|
+
fontSize: "1.5rem",
|
|
1530
|
+
fontWeight: 600,
|
|
1531
|
+
letterSpacing: "-0.01em"
|
|
1532
|
+
};
|
|
1533
|
+
var leadStyle = {
|
|
1534
|
+
margin: "0.75rem auto 2rem",
|
|
1535
|
+
maxWidth: "24rem",
|
|
1536
|
+
textAlign: "center",
|
|
1537
|
+
fontSize: "0.875rem",
|
|
1538
|
+
lineHeight: 1.5,
|
|
1539
|
+
color: tokens.mutedForeground
|
|
1540
|
+
};
|
|
1541
|
+
var cardStyle2 = {
|
|
1542
|
+
boxSizing: "border-box",
|
|
1543
|
+
marginBottom: "1rem",
|
|
1544
|
+
padding: "1.25rem",
|
|
1545
|
+
border: `1px solid ${tokens.border}`,
|
|
1546
|
+
borderRadius: tokens.radius,
|
|
1547
|
+
background: tokens.card
|
|
1548
|
+
};
|
|
1549
|
+
var cardTitleStyle = {
|
|
1550
|
+
margin: "0 0 0.375rem",
|
|
1551
|
+
fontSize: "1rem",
|
|
1552
|
+
fontWeight: 600
|
|
1553
|
+
};
|
|
1554
|
+
var cardTextStyle = {
|
|
1555
|
+
margin: 0,
|
|
1556
|
+
fontSize: "0.875rem",
|
|
1557
|
+
lineHeight: 1.5,
|
|
1558
|
+
color: tokens.mutedForeground
|
|
1559
|
+
};
|
|
1560
|
+
var codeStyle = {
|
|
1561
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
1562
|
+
fontSize: "0.8125rem",
|
|
1563
|
+
color: tokens.foreground
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
// src/app/chrome/widget-title.ts
|
|
1567
|
+
function widgetTitle() {
|
|
1568
|
+
const title = typeof document === "undefined" ? "" : document.title.trim();
|
|
1569
|
+
return title || "Grist widget";
|
|
1523
1570
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1571
|
+
|
|
1572
|
+
// src/app/chrome/local-dev-page.tsx
|
|
1573
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1574
|
+
function useHasDemo() {
|
|
1575
|
+
const [has, setHas] = (0, import_react5.useState)(null);
|
|
1576
|
+
(0, import_react5.useEffect)(() => {
|
|
1577
|
+
if (typeof fetch !== "function") return;
|
|
1578
|
+
let live = true;
|
|
1579
|
+
fetch("demo/demo.grist", { method: "HEAD" }).then((res) => live && setHas(res.ok)).catch(() => live && setHas(false));
|
|
1580
|
+
return () => {
|
|
1581
|
+
live = false;
|
|
1582
|
+
};
|
|
1583
|
+
}, []);
|
|
1584
|
+
return has;
|
|
1535
1585
|
}
|
|
1536
|
-
function
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1586
|
+
function LocalDevPage() {
|
|
1587
|
+
const hasDemo = useHasDemo();
|
|
1588
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { style: pageStyle, children: [
|
|
1589
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: eyebrowStyle, children: [
|
|
1590
|
+
widgetTitle(),
|
|
1591
|
+
" \xB7 local dev"
|
|
1592
|
+
] }),
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { style: headingStyle, children: "Not embedded in Grist" }),
|
|
1594
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: leadStyle, children: "This is the local dev server: there's no Grist here to render the widget against." }),
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Open it in Grist, in this browser" }),
|
|
1597
|
+
hasDemo === false ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
|
|
1598
|
+
"Run ",
|
|
1599
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "pnpm demo --save" }),
|
|
1600
|
+
" once (it needs Docker): it writes",
|
|
1601
|
+
" ",
|
|
1602
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "public/demo/" }),
|
|
1603
|
+
", a document for this widget. Then",
|
|
1604
|
+
" ",
|
|
1605
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: "demo/index.html", children: "demo/index.html" }),
|
|
1606
|
+
" opens it in real Grist, running in the browser, on this dev server's widget: edits reload in place."
|
|
1607
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
|
|
1608
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: "demo/index.html", children: "Open demo/index.html" }),
|
|
1609
|
+
": real Grist running in the browser (no server, nothing saved), with this dev server's widget, so edits reload in place. Its document is",
|
|
1610
|
+
" ",
|
|
1611
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "public/demo/demo.grist" }),
|
|
1612
|
+
": after changing the declared columns, run",
|
|
1613
|
+
" ",
|
|
1614
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "pnpm demo --save" }),
|
|
1615
|
+
" (Docker) to regenerate it."
|
|
1616
|
+
] })
|
|
1617
|
+
] }),
|
|
1618
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
|
|
1619
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Check it against real Grist" }),
|
|
1620
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
|
|
1621
|
+
"Push to ",
|
|
1622
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "dev" }),
|
|
1623
|
+
", then open its ",
|
|
1624
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "/dev/" }),
|
|
1625
|
+
" URL in a custom widget of a real Grist document: later pushes reload it there within seconds. Locally,",
|
|
1626
|
+
" ",
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "pnpm test" }),
|
|
1628
|
+
" runs the widget against an emulated Grist."
|
|
1629
|
+
] })
|
|
1630
|
+
] }),
|
|
1631
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Starting from another example?" }),
|
|
1633
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
|
|
1634
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "npx create-grist-widget list" }),
|
|
1635
|
+
" shows the examples, and",
|
|
1636
|
+
" ",
|
|
1637
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "npx create-grist-widget use <example>" }),
|
|
1638
|
+
" swaps this widget for one."
|
|
1639
|
+
] })
|
|
1640
|
+
] })
|
|
1641
|
+
] });
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/app/chrome/release-page.tsx
|
|
1645
|
+
var import_react7 = require("react");
|
|
1646
|
+
|
|
1647
|
+
// src/app/deploy-path.ts
|
|
1648
|
+
var VERSION_SEGMENT = /^v(\d+\.\d+\.\d+(?:-[\w.]+)?)$/;
|
|
1649
|
+
function parseDeployPath(pathname) {
|
|
1650
|
+
const segments = pathname.replace(/\/+$/, "").split("/");
|
|
1651
|
+
const last = segments[segments.length - 1] ?? "";
|
|
1652
|
+
let channel = null;
|
|
1653
|
+
if (last === "latest") channel = { kind: "latest" };
|
|
1654
|
+
else if (last === "dev") channel = { kind: "dev" };
|
|
1655
|
+
else {
|
|
1656
|
+
const match = VERSION_SEGMENT.exec(last);
|
|
1657
|
+
if (match) channel = { kind: "version", version: match[1] };
|
|
1562
1658
|
}
|
|
1659
|
+
if (!channel) return { channel: { kind: "latest" }, basePath: withTrailingSlash(pathname) };
|
|
1660
|
+
return { channel, basePath: withTrailingSlash(segments.slice(0, -1).join("/")) };
|
|
1563
1661
|
}
|
|
1564
|
-
function
|
|
1565
|
-
|
|
1566
|
-
return ["E", "Error", message2];
|
|
1662
|
+
function deployChannelLabel(channel) {
|
|
1663
|
+
return channel.kind === "version" ? `v${channel.version}` : channel.kind;
|
|
1567
1664
|
}
|
|
1568
|
-
function
|
|
1569
|
-
|
|
1570
|
-
const tableIds = [];
|
|
1571
|
-
const labels = [];
|
|
1572
|
-
let nextId = 1;
|
|
1573
|
-
for (const [tableId, table] of state.tables.entries()) {
|
|
1574
|
-
ids.push(nextId);
|
|
1575
|
-
tableIds.push(tableId);
|
|
1576
|
-
labels.push(table.label ?? null);
|
|
1577
|
-
nextId += 1;
|
|
1578
|
-
}
|
|
1579
|
-
return {
|
|
1580
|
-
id: ids,
|
|
1581
|
-
tableId: tableIds,
|
|
1582
|
-
// Optional human-readable label; real Grist keeps more fields, but these
|
|
1583
|
-
// are sufficient for `buildReplicaDocumentFromDocApi` to resolve table ids.
|
|
1584
|
-
label: labels
|
|
1585
|
-
};
|
|
1665
|
+
function withTrailingSlash(path) {
|
|
1666
|
+
return path.endsWith("/") ? path : `${path}/`;
|
|
1586
1667
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
const
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
}
|
|
1608
|
-
for (const [tableId, table] of state.tables.entries()) {
|
|
1609
|
-
const parentInternalId = tableIdToInternalId.get(tableId);
|
|
1610
|
-
if (parentInternalId === void 0) continue;
|
|
1611
|
-
for (const [colId, col] of Object.entries(table.columns)) {
|
|
1612
|
-
parentIds.push(parentInternalId);
|
|
1613
|
-
colIds.push(colId);
|
|
1614
|
-
const ref = typeof col.colRef === "number" ? col.colRef : columnIds.length + 1;
|
|
1615
|
-
columnIds.push(ref);
|
|
1616
|
-
types.push(col.type ?? null);
|
|
1617
|
-
labels.push(col.label ?? null);
|
|
1618
|
-
parentPos.push(col.parentPos ?? null);
|
|
1619
|
-
isFormula.push(Boolean(col.isFormula));
|
|
1620
|
-
const formula = typeof col.formula === "string" ? col.formula : null;
|
|
1621
|
-
formulas.push(formula);
|
|
1622
|
-
descriptions.push(col.description ?? null);
|
|
1623
|
-
widgetOptions.push(col.widgetOptions ?? null);
|
|
1624
|
-
displayCol.push(typeof col.displayColRef === "number" ? col.displayColRef : null);
|
|
1625
|
-
visibleCol.push(typeof col.visibleColRef === "number" ? col.visibleColRef : null);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
return {
|
|
1629
|
-
parentId: parentIds,
|
|
1630
|
-
colId: colIds,
|
|
1631
|
-
id: columnIds,
|
|
1632
|
-
type: types,
|
|
1633
|
-
label: labels,
|
|
1634
|
-
parentPos,
|
|
1635
|
-
isFormula,
|
|
1636
|
-
formula: formulas,
|
|
1637
|
-
description: descriptions,
|
|
1638
|
-
// Also expose legacy `descr` for robustness.
|
|
1639
|
-
descr: descriptions,
|
|
1640
|
-
widgetOptions,
|
|
1641
|
-
displayCol,
|
|
1642
|
-
visibleCol
|
|
1643
|
-
};
|
|
1668
|
+
|
|
1669
|
+
// src/app/released-versions.ts
|
|
1670
|
+
var import_react6 = require("react");
|
|
1671
|
+
function useReleasedVersions(pathname = typeof window === "undefined" ? "/" : window.location.pathname) {
|
|
1672
|
+
const url = `${parseDeployPath(pathname).basePath}versions.json`;
|
|
1673
|
+
const [result, setResult] = (0, import_react6.useState)({ versions: null, error: false });
|
|
1674
|
+
(0, import_react6.useEffect)(() => {
|
|
1675
|
+
let cancelled = false;
|
|
1676
|
+
fetch(url, { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`))).then((data) => {
|
|
1677
|
+
if (cancelled) return;
|
|
1678
|
+
if (!Array.isArray(data)) throw new Error("versions.json is not an array");
|
|
1679
|
+
setResult({ versions: data, error: false });
|
|
1680
|
+
}).catch(() => {
|
|
1681
|
+
if (!cancelled) setResult({ versions: null, error: true });
|
|
1682
|
+
});
|
|
1683
|
+
return () => {
|
|
1684
|
+
cancelled = true;
|
|
1685
|
+
};
|
|
1686
|
+
}, [url]);
|
|
1687
|
+
return result;
|
|
1644
1688
|
}
|
|
1645
1689
|
|
|
1646
|
-
// src/
|
|
1647
|
-
var
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
}
|
|
1665
|
-
return { retValues, changedTableIds: [...changed] };
|
|
1690
|
+
// src/app/chrome/release-page.tsx
|
|
1691
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1692
|
+
function ReleasePage({ channel }) {
|
|
1693
|
+
const label = deployChannelLabel(channel);
|
|
1694
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("main", { style: pageStyle, children: [
|
|
1695
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: eyebrowStyle, children: [
|
|
1696
|
+
widgetTitle(),
|
|
1697
|
+
" \xB7 ",
|
|
1698
|
+
label
|
|
1699
|
+
] }),
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { style: headingStyle, children: "Grist isn't loaded here" }),
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: leadStyle, children: [
|
|
1702
|
+
"This is the ",
|
|
1703
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: codeStyle, children: label }),
|
|
1704
|
+
" build, opened outside Grist. Hover a version to see when it was released."
|
|
1705
|
+
] }),
|
|
1706
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChannelLinks, { current: channel }),
|
|
1707
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InstallUrl, {})
|
|
1708
|
+
] });
|
|
1666
1709
|
}
|
|
1667
|
-
function
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
return addColumn(state, args);
|
|
1686
|
-
case "RemoveColumn":
|
|
1687
|
-
return removeColumn(state, args, changed);
|
|
1688
|
-
case "RenameColumn":
|
|
1689
|
-
return renameColumn(state, args, changed);
|
|
1690
|
-
case "ModifyColumn":
|
|
1691
|
-
return modifyColumn(state, args, changed);
|
|
1692
|
-
case "AddTable":
|
|
1693
|
-
return addTable(state, args);
|
|
1694
|
-
case "RemoveTable":
|
|
1695
|
-
return removeTable(state, args);
|
|
1696
|
-
case "RenameTable":
|
|
1697
|
-
return renameTable(state, args);
|
|
1698
|
-
default:
|
|
1699
|
-
throw new EmulatorActionError(action, `Unsupported user action: ${String(name)}`);
|
|
1700
|
-
}
|
|
1710
|
+
function ChannelLinks({ current }) {
|
|
1711
|
+
const { basePath } = parseDeployPath(window.location.pathname);
|
|
1712
|
+
const { versions } = useReleasedVersions();
|
|
1713
|
+
const isCurrent = (channel) => channel.kind === current.kind && (channel.kind !== "version" || current.kind === "version" && channel.version === current.version);
|
|
1714
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { "aria-label": "Versions", style: { display: "flex", flexWrap: "wrap", justifyContent: "center", gap: "0.5rem", marginBottom: "2rem" }, children: [
|
|
1715
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChannelLink, { href: `${basePath}latest/`, active: isCurrent({ kind: "latest" }), children: "latest" }),
|
|
1716
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChannelLink, { href: `${basePath}dev/`, active: isCurrent({ kind: "dev" }), children: "dev" }),
|
|
1717
|
+
versions?.map((v) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1718
|
+
ChannelLink,
|
|
1719
|
+
{
|
|
1720
|
+
href: `${basePath}v${v.version}/`,
|
|
1721
|
+
title: formatDate(v.publishedAt),
|
|
1722
|
+
active: isCurrent({ kind: "version", version: v.version }),
|
|
1723
|
+
children: `v${v.version}`
|
|
1724
|
+
},
|
|
1725
|
+
v.version
|
|
1726
|
+
))
|
|
1727
|
+
] });
|
|
1701
1728
|
}
|
|
1702
|
-
function
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1729
|
+
function ChannelLink({
|
|
1730
|
+
href,
|
|
1731
|
+
title,
|
|
1732
|
+
active,
|
|
1733
|
+
children
|
|
1734
|
+
}) {
|
|
1735
|
+
const style = {
|
|
1736
|
+
padding: "0.25rem 0.75rem",
|
|
1737
|
+
border: `1px solid ${active ? tokens.primary : tokens.border}`,
|
|
1738
|
+
borderRadius: "9999px",
|
|
1739
|
+
background: active ? tokens.primary : "transparent",
|
|
1740
|
+
color: active ? tokens.primaryForeground : tokens.mutedForeground,
|
|
1741
|
+
fontSize: "0.75rem",
|
|
1742
|
+
fontWeight: 500,
|
|
1743
|
+
textDecoration: "none"
|
|
1744
|
+
};
|
|
1745
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href, title, "aria-current": active ? "page" : void 0, style, children });
|
|
1716
1746
|
}
|
|
1717
|
-
function
|
|
1718
|
-
const
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
const out = [];
|
|
1726
|
-
ensureColumns(table, cols, true);
|
|
1727
|
-
for (let i = 0; i < length; i++) {
|
|
1728
|
-
const id = typeof ids[i] === "number" ? ids[i] : nextRowId(table);
|
|
1729
|
-
const row = { id };
|
|
1730
|
-
for (const colId of Object.keys(cols)) row[colId] = cols[colId][i];
|
|
1731
|
-
evaluateRowFormulas(state, table, row);
|
|
1732
|
-
syncRefListDisplayHelpers(state, table, row);
|
|
1733
|
-
table.rows.push(row);
|
|
1734
|
-
out.push(id);
|
|
1747
|
+
function InstallUrl() {
|
|
1748
|
+
const url = window.location.href;
|
|
1749
|
+
const [copied, setCopied] = (0, import_react7.useState)(false);
|
|
1750
|
+
function copy() {
|
|
1751
|
+
void navigator.clipboard?.writeText(url).then(() => {
|
|
1752
|
+
setCopied(true);
|
|
1753
|
+
setTimeout(() => setCopied(false), 1500);
|
|
1754
|
+
});
|
|
1735
1755
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1756
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { style: cardStyle2, children: [
|
|
1757
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: cardTitleStyle, children: "Use it as a Grist custom widget" }),
|
|
1758
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { ...cardTextStyle, marginBottom: "1rem" }, children: "Paste this URL into a custom widget's URL field in Grist." }),
|
|
1759
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
|
|
1760
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1761
|
+
"input",
|
|
1762
|
+
{
|
|
1763
|
+
readOnly: true,
|
|
1764
|
+
value: url,
|
|
1765
|
+
"aria-label": "Widget URL",
|
|
1766
|
+
onFocus: (event) => event.target.select(),
|
|
1767
|
+
style: {
|
|
1768
|
+
flex: 1,
|
|
1769
|
+
minWidth: 0,
|
|
1770
|
+
padding: "0.5rem 0.75rem",
|
|
1771
|
+
border: `1px solid ${tokens.border}`,
|
|
1772
|
+
borderRadius: tokens.radius,
|
|
1773
|
+
background: "transparent",
|
|
1774
|
+
color: tokens.foreground,
|
|
1775
|
+
font: "inherit",
|
|
1776
|
+
fontSize: "0.875rem"
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
),
|
|
1780
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1781
|
+
"button",
|
|
1782
|
+
{
|
|
1783
|
+
type: "button",
|
|
1784
|
+
onClick: copy,
|
|
1785
|
+
style: {
|
|
1786
|
+
padding: "0.5rem 1rem",
|
|
1787
|
+
border: "none",
|
|
1788
|
+
borderRadius: tokens.radius,
|
|
1789
|
+
background: tokens.primary,
|
|
1790
|
+
color: tokens.primaryForeground,
|
|
1791
|
+
font: "inherit",
|
|
1792
|
+
fontSize: "0.875rem",
|
|
1793
|
+
fontWeight: 500,
|
|
1794
|
+
cursor: "pointer"
|
|
1795
|
+
},
|
|
1796
|
+
children: copied ? "Copied" : "Copy"
|
|
1797
|
+
}
|
|
1798
|
+
)
|
|
1799
|
+
] })
|
|
1800
|
+
] });
|
|
1754
1801
|
}
|
|
1755
|
-
function
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1758
|
-
const cols = normalizeColumnarForTable(
|
|
1759
|
-
table,
|
|
1760
|
-
columnar ?? {}
|
|
1761
|
-
);
|
|
1762
|
-
ensureColumns(table, cols, true);
|
|
1763
|
-
ids.forEach((id, idx) => {
|
|
1764
|
-
const row = findRow(table, id);
|
|
1765
|
-
if (!row) throw new EmulatorActionError(["BulkUpdateRecord", tableId, ids, cols], `Row ${id} not found in ${table.tableId}`);
|
|
1766
|
-
for (const colId of Object.keys(cols)) row[colId] = cols[colId][idx];
|
|
1767
|
-
evaluateRowFormulas(state, table, row);
|
|
1768
|
-
syncRefListDisplayHelpers(state, table, row);
|
|
1769
|
-
});
|
|
1770
|
-
changed.add(table.tableId);
|
|
1771
|
-
return null;
|
|
1802
|
+
function formatDate(iso) {
|
|
1803
|
+
const date = new Date(iso);
|
|
1804
|
+
return Number.isNaN(date.getTime()) ? iso : date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
1772
1805
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1806
|
+
|
|
1807
|
+
// src/sdk/helpers/grist-sdk-alerts.ts
|
|
1808
|
+
var TITLE = {
|
|
1809
|
+
"column-mapping": "Column mapping",
|
|
1810
|
+
"mapping-pending": "Column mapping",
|
|
1811
|
+
"section-not-linked": "Widget linking",
|
|
1812
|
+
"source-not-wired": "Widget linking",
|
|
1813
|
+
"action-error": "Action error",
|
|
1814
|
+
"access-insufficient": "Access level",
|
|
1815
|
+
"map-back-skip": "Write skipped",
|
|
1816
|
+
"cell-issues": "Unexpected values"
|
|
1817
|
+
};
|
|
1818
|
+
var SEVERITY = {
|
|
1819
|
+
"column-mapping": "warning",
|
|
1820
|
+
"mapping-pending": "info",
|
|
1821
|
+
"section-not-linked": "warning",
|
|
1822
|
+
"source-not-wired": "warning",
|
|
1823
|
+
"action-error": "error",
|
|
1824
|
+
"access-insufficient": "error",
|
|
1825
|
+
"map-back-skip": "warning",
|
|
1826
|
+
"cell-issues": "warning"
|
|
1827
|
+
};
|
|
1828
|
+
var alert = (kind, message2, ariaRole = "status") => ({
|
|
1829
|
+
id: kind,
|
|
1830
|
+
kind,
|
|
1831
|
+
severity: SEVERITY[kind],
|
|
1832
|
+
title: TITLE[kind],
|
|
1833
|
+
ariaRole,
|
|
1834
|
+
message: message2
|
|
1835
|
+
});
|
|
1836
|
+
function isWidgetSectionNotLinked(interaction) {
|
|
1837
|
+
const linking = parseGristWidgetLinkingInfo(interaction?.linking);
|
|
1838
|
+
return Boolean(linking) && linking.asTarget == null;
|
|
1783
1839
|
}
|
|
1784
|
-
function
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
if (
|
|
1789
|
-
|
|
1840
|
+
function getGristSdkAlerts(state, widget, options = {}) {
|
|
1841
|
+
if (deriveStatus(state) !== "ready") return [];
|
|
1842
|
+
const out = [];
|
|
1843
|
+
const mapping = deriveColumnMappingStatus(widget.columns, state.mappings);
|
|
1844
|
+
if (mapping.pending) out.push(alert("mapping-pending", "Waiting for Grist to report column mappings."));
|
|
1845
|
+
else if (!mapping.ok) {
|
|
1846
|
+
const parts = ["Column mapping is incomplete."];
|
|
1847
|
+
if (mapping.missing.length) parts.push(`Required columns: ${mapping.missing.join(", ")}.`);
|
|
1848
|
+
if (mapping.emptyMultiples.length) parts.push(`Assign at least one sheet column for: ${mapping.emptyMultiples.join(", ")}.`);
|
|
1849
|
+
out.push(alert("column-mapping", parts.join(" ")));
|
|
1790
1850
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1851
|
+
const failure = state.actionError ?? state.readError;
|
|
1852
|
+
if (failure && isAccessInsufficientError(failure)) {
|
|
1853
|
+
out.push(
|
|
1854
|
+
alert(
|
|
1855
|
+
"access-insufficient",
|
|
1856
|
+
'This operation requires "Full document access". Set the Access level to "Full document access" in the Custom widget configuration panel.',
|
|
1857
|
+
"alert"
|
|
1858
|
+
)
|
|
1859
|
+
);
|
|
1860
|
+
} else if (state.actionError) {
|
|
1861
|
+
out.push(alert("action-error", state.actionError, "alert"));
|
|
1801
1862
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
const table = getTable(state, requireString(tableId, "RemoveColumn.tableId"));
|
|
1806
|
-
const col = requireString(colId, "RemoveColumn.colId");
|
|
1807
|
-
delete table.columns[col];
|
|
1808
|
-
for (const row of table.rows) delete row[col];
|
|
1809
|
-
changed.add(table.tableId);
|
|
1810
|
-
return null;
|
|
1811
|
-
}
|
|
1812
|
-
function renameColumn(state, [tableId, colId, newColId], changed) {
|
|
1813
|
-
const table = getTable(state, requireString(tableId, "RenameColumn.tableId"));
|
|
1814
|
-
const from = requireString(colId, "RenameColumn.colId");
|
|
1815
|
-
const to = requireString(newColId, "RenameColumn.newColId");
|
|
1816
|
-
if (!table.columns[from]) throw new EmulatorActionError(["RenameColumn", tableId, from, to], `Column ${from} not found`);
|
|
1817
|
-
if (from === to) return null;
|
|
1818
|
-
table.columns[to] = table.columns[from];
|
|
1819
|
-
delete table.columns[from];
|
|
1820
|
-
for (const row of table.rows) {
|
|
1821
|
-
if (from in row) {
|
|
1822
|
-
row[to] = row[from];
|
|
1823
|
-
delete row[from];
|
|
1824
|
-
}
|
|
1863
|
+
if (state.mapBackSkipped.length) {
|
|
1864
|
+
const list = state.mapBackSkipped.join(", ");
|
|
1865
|
+
out.push(alert("map-back-skip", `mapBack skipped multi-mapped column${state.mapBackSkipped.length === 1 ? "" : "s"}: ${list}.`));
|
|
1825
1866
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
if (!table.columns[col]) throw new EmulatorActionError(["ModifyColumn", tableId, colId], `Column ${col} not found`);
|
|
1833
|
-
table.columns[col] = { ...table.columns[col], ...info };
|
|
1834
|
-
changed.add(table.tableId);
|
|
1835
|
-
return null;
|
|
1836
|
-
}
|
|
1837
|
-
function addTable(state, [tableId, columns]) {
|
|
1838
|
-
const id = requireString(tableId, "AddTable.tableId");
|
|
1839
|
-
if (state.tables.has(id)) throw new EmulatorActionError(["AddTable", tableId], `Table ${id} already exists`);
|
|
1840
|
-
const cols = {};
|
|
1841
|
-
for (const c of columns ?? []) {
|
|
1842
|
-
cols[c.id] = { ...c };
|
|
1867
|
+
const cellIssues = widget.cellIssues ?? [];
|
|
1868
|
+
if (cellIssues.length) {
|
|
1869
|
+
const first = cellIssues[0];
|
|
1870
|
+
const where = first.column ? `${first.column} in row ${first.rowId}` : `row ${first.rowId}`;
|
|
1871
|
+
const more = cellIssues.length > 1 ? ` (and ${cellIssues.length - 1} more)` : "";
|
|
1872
|
+
out.push(alert("cell-issues", `${where}: ${first.message}${more}. Check the column's type, or the cell, in Grist.`));
|
|
1843
1873
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
state.selection.tableId = fallback;
|
|
1854
|
-
state.selection.rowId = null;
|
|
1874
|
+
const linking = parseGristWidgetLinkingInfo(state.interaction?.linking);
|
|
1875
|
+
if (widget.allowSelectBy) {
|
|
1876
|
+
if (linking && !linking.asSource) {
|
|
1877
|
+
out.push(
|
|
1878
|
+
alert(
|
|
1879
|
+
"source-not-wired",
|
|
1880
|
+
"This widget is configured as a selector (allowSelectBy) but no other section is using it as a link source yet. Link another section to read from this widget."
|
|
1881
|
+
)
|
|
1882
|
+
);
|
|
1855
1883
|
}
|
|
1884
|
+
} else if (isWidgetSectionNotLinked(state.interaction)) {
|
|
1885
|
+
const hint = options.sectionNotLinkedHint?.trim();
|
|
1886
|
+
out.push(
|
|
1887
|
+
alert(
|
|
1888
|
+
"section-not-linked",
|
|
1889
|
+
"This widget section is not linked to a table or selector. Link it from the page settings so the selected row is sent to the widget." + (hint ? ` ${hint}` : "")
|
|
1890
|
+
)
|
|
1891
|
+
);
|
|
1856
1892
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
function renameTable(state, [from, to]) {
|
|
1860
|
-
const fromId = requireString(from, "RenameTable.from");
|
|
1861
|
-
const toId = requireString(to, "RenameTable.to");
|
|
1862
|
-
const table = state.tables.get(fromId);
|
|
1863
|
-
if (!table) throw new EmulatorActionError(["RenameTable", fromId], `Table ${fromId} not found`);
|
|
1864
|
-
state.tables.delete(fromId);
|
|
1865
|
-
table.tableId = toId;
|
|
1866
|
-
state.tables.set(toId, table);
|
|
1867
|
-
if (state.selection.tableId === fromId) state.selection.tableId = toId;
|
|
1868
|
-
return null;
|
|
1893
|
+
const suppressed = widget.suppressAlerts ?? [];
|
|
1894
|
+
return out.filter((a) => !suppressed.includes(a.kind));
|
|
1869
1895
|
}
|
|
1870
|
-
function
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1896
|
+
function useGristSdkAlerts(options) {
|
|
1897
|
+
const { connection, state, cellIssues } = useNearestGristState();
|
|
1898
|
+
if (!connection) return [];
|
|
1899
|
+
const { columns, allowSelectBy, suppressAlerts } = connection.runtime.options;
|
|
1900
|
+
return getGristSdkAlerts(state, { columns, allowSelectBy, suppressAlerts, cellIssues }, options);
|
|
1877
1901
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1902
|
+
|
|
1903
|
+
// src/app/chrome/widget-alerts.tsx
|
|
1904
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1905
|
+
var ALERT_OPTIONS = {
|
|
1906
|
+
sectionNotLinkedHint: "In Grist, select this widget section on the page, then use Link to connect a table, card, or chart view as the selector."
|
|
1907
|
+
};
|
|
1908
|
+
function WidgetAlerts({ children }) {
|
|
1909
|
+
const alerts = useGristSdkAlerts(ALERT_OPTIONS);
|
|
1910
|
+
if (alerts.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
|
|
1911
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
|
|
1912
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", padding: "0.75rem 0.75rem 0" }, children: alerts.map((alert2) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WidgetAlert, { alert: alert2 }, alert2.id)) }),
|
|
1913
|
+
children
|
|
1914
|
+
] });
|
|
1889
1915
|
}
|
|
1890
|
-
function
|
|
1891
|
-
|
|
1892
|
-
return 0
|
|
1916
|
+
function WidgetAlert({ alert: alert2 }) {
|
|
1917
|
+
const linkIcon = alert2.kind === "section-not-linked" || alert2.kind === "source-not-wired";
|
|
1918
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { role: alert2.ariaRole, style: { ...alertStyle, ...SEVERITY2[alert2.severity] }, children: [
|
|
1919
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-hidden": true, style: { flexShrink: 0, marginTop: "0.125rem" }, children: linkIcon ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LinkIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WarningIcon, {}) }),
|
|
1920
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
1921
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { margin: 0, fontWeight: 500 }, children: alert2.title }),
|
|
1922
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { margin: "0.25rem 0 0", color: tokens.mutedForeground }, children: alert2.message })
|
|
1923
|
+
] })
|
|
1924
|
+
] });
|
|
1893
1925
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1926
|
+
var alertStyle = {
|
|
1927
|
+
boxSizing: "border-box",
|
|
1928
|
+
display: "flex",
|
|
1929
|
+
gap: "0.75rem",
|
|
1930
|
+
padding: "0.75rem 1rem",
|
|
1931
|
+
border: `1px solid ${tokens.border}`,
|
|
1932
|
+
borderRadius: tokens.radius,
|
|
1933
|
+
color: tokens.foreground,
|
|
1934
|
+
fontSize: "0.875rem",
|
|
1935
|
+
lineHeight: 1.5
|
|
1936
|
+
};
|
|
1937
|
+
var SEVERITY2 = {
|
|
1938
|
+
error: { borderColor: tokens.destructive, color: tokens.destructive },
|
|
1939
|
+
warning: { borderColor: "rgb(245 158 11 / 0.4)", background: "rgb(245 158 11 / 0.1)" },
|
|
1940
|
+
info: { background: "transparent" }
|
|
1941
|
+
};
|
|
1942
|
+
var ICON = {
|
|
1943
|
+
width: 16,
|
|
1944
|
+
height: 16,
|
|
1945
|
+
viewBox: "0 0 24 24",
|
|
1946
|
+
fill: "none",
|
|
1947
|
+
stroke: "currentColor",
|
|
1948
|
+
strokeWidth: 2,
|
|
1949
|
+
strokeLinecap: "round",
|
|
1950
|
+
strokeLinejoin: "round"
|
|
1951
|
+
};
|
|
1952
|
+
function WarningIcon() {
|
|
1953
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { ...ICON, children: [
|
|
1954
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }),
|
|
1955
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 9v4" }),
|
|
1956
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 17h.01" })
|
|
1957
|
+
] });
|
|
1897
1958
|
}
|
|
1898
|
-
function
|
|
1899
|
-
|
|
1900
|
-
|
|
1959
|
+
function LinkIcon() {
|
|
1960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { ...ICON, children: [
|
|
1961
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }),
|
|
1962
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }),
|
|
1963
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "8", x2: "16", y1: "12", y2: "12" })
|
|
1964
|
+
] });
|
|
1901
1965
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1966
|
+
|
|
1967
|
+
// src/app/widget-theme.tsx
|
|
1968
|
+
var import_react8 = require("react");
|
|
1969
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1970
|
+
var STORAGE_KEY = "theme";
|
|
1971
|
+
var DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
1972
|
+
var THEMES = ["light", "dark", "system"];
|
|
1973
|
+
var ThemeContext = (0, import_react8.createContext)(null);
|
|
1974
|
+
function EmbeddedThemeProvider({ children }) {
|
|
1975
|
+
const gristTheme = useNearestGristState().state.theme;
|
|
1976
|
+
const system = useSystemTheme(gristTheme === null);
|
|
1977
|
+
const resolvedTheme = gristTheme ?? system;
|
|
1978
|
+
useApplyTheme(resolvedTheme);
|
|
1979
|
+
const value = (0, import_react8.useMemo)(
|
|
1980
|
+
() => ({ theme: gristTheme ?? "system", resolvedTheme, setTheme: noop }),
|
|
1981
|
+
[gristTheme, resolvedTheme]
|
|
1982
|
+
);
|
|
1983
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ThemeContext.Provider, { value, children });
|
|
1908
1984
|
}
|
|
1909
|
-
function
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1985
|
+
function StandaloneThemeProvider({ children }) {
|
|
1986
|
+
const [theme, setThemeState] = (0, import_react8.useState)(() => readStoredTheme() ?? "system");
|
|
1987
|
+
const system = useSystemTheme(theme === "system");
|
|
1988
|
+
const resolvedTheme = theme === "system" ? system : theme;
|
|
1989
|
+
useApplyTheme(resolvedTheme);
|
|
1990
|
+
const setTheme = (0, import_react8.useCallback)((next) => {
|
|
1991
|
+
writeStoredTheme(next);
|
|
1992
|
+
setThemeState(next);
|
|
1993
|
+
}, []);
|
|
1994
|
+
(0, import_react8.useEffect)(() => {
|
|
1995
|
+
const onKeyDown = (event) => {
|
|
1996
|
+
if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
1997
|
+
if (event.key.toLowerCase() !== "d" || isEditableTarget(event.target)) return;
|
|
1998
|
+
setThemeState((current) => {
|
|
1999
|
+
const currentResolved = current === "system" ? systemTheme() : current;
|
|
2000
|
+
const next = currentResolved === "dark" ? "light" : "dark";
|
|
2001
|
+
writeStoredTheme(next);
|
|
2002
|
+
return next;
|
|
2003
|
+
});
|
|
2004
|
+
};
|
|
2005
|
+
window.addEventListener("keydown", onKeyDown);
|
|
2006
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2007
|
+
}, []);
|
|
2008
|
+
(0, import_react8.useEffect)(() => {
|
|
2009
|
+
const onStorage = (event) => {
|
|
2010
|
+
if (event.key !== STORAGE_KEY) return;
|
|
2011
|
+
setThemeState(isTheme(event.newValue) ? event.newValue : "system");
|
|
2012
|
+
};
|
|
2013
|
+
window.addEventListener("storage", onStorage);
|
|
2014
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
2015
|
+
}, []);
|
|
2016
|
+
const value = (0, import_react8.useMemo)(
|
|
2017
|
+
() => ({ theme, resolvedTheme, setTheme }),
|
|
2018
|
+
[theme, resolvedTheme, setTheme]
|
|
2019
|
+
);
|
|
2020
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ThemeContext.Provider, { value, children });
|
|
1916
2021
|
}
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2022
|
+
function useSystemTheme(active) {
|
|
2023
|
+
const [theme, setTheme] = (0, import_react8.useState)(systemTheme);
|
|
2024
|
+
(0, import_react8.useEffect)(() => {
|
|
2025
|
+
if (!active || typeof window.matchMedia !== "function") return;
|
|
2026
|
+
const query = window.matchMedia(DARK_QUERY);
|
|
2027
|
+
const onChange = () => setTheme(query.matches ? "dark" : "light");
|
|
2028
|
+
onChange();
|
|
2029
|
+
query.addEventListener("change", onChange);
|
|
2030
|
+
return () => query.removeEventListener("change", onChange);
|
|
2031
|
+
}, [active]);
|
|
2032
|
+
return theme;
|
|
1922
2033
|
}
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
}
|
|
1932
|
-
const fromTuple = coerceGristDocApiRefRowId(value);
|
|
1933
|
-
if (fromTuple !== void 0 && fromTuple > 0) return Math.trunc(fromTuple);
|
|
1934
|
-
return toPositiveInt(value);
|
|
2034
|
+
function useApplyTheme(theme) {
|
|
2035
|
+
(0, import_react8.useEffect)(() => {
|
|
2036
|
+
const root = document.documentElement;
|
|
2037
|
+
if (root.classList.contains(theme) && !root.classList.contains(other(theme))) return;
|
|
2038
|
+
const restoreTransitions = suspendTransitions();
|
|
2039
|
+
root.classList.remove(other(theme));
|
|
2040
|
+
root.classList.add(theme);
|
|
2041
|
+
restoreTransitions();
|
|
2042
|
+
}, [theme]);
|
|
1935
2043
|
}
|
|
1936
|
-
function
|
|
1937
|
-
|
|
2044
|
+
function systemTheme() {
|
|
2045
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
|
|
2046
|
+
return window.matchMedia(DARK_QUERY).matches ? "dark" : "light";
|
|
1938
2047
|
}
|
|
1939
|
-
function
|
|
1940
|
-
|
|
1941
|
-
|
|
2048
|
+
function other(theme) {
|
|
2049
|
+
return theme === "dark" ? "light" : "dark";
|
|
2050
|
+
}
|
|
2051
|
+
function suspendTransitions() {
|
|
2052
|
+
const style = document.createElement("style");
|
|
2053
|
+
style.appendChild(
|
|
2054
|
+
document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;transition:none!important}")
|
|
1942
2055
|
);
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2056
|
+
document.head.appendChild(style);
|
|
2057
|
+
return () => {
|
|
2058
|
+
window.getComputedStyle(document.body);
|
|
2059
|
+
requestAnimationFrame(() => requestAnimationFrame(() => style.remove()));
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function isEditableTarget(target) {
|
|
2063
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
2064
|
+
return target.isContentEditable || target.closest("input, textarea, select, [contenteditable='true']") !== null;
|
|
2065
|
+
}
|
|
2066
|
+
function isTheme(value) {
|
|
2067
|
+
return value !== null && THEMES.includes(value);
|
|
2068
|
+
}
|
|
2069
|
+
function readStoredTheme() {
|
|
2070
|
+
try {
|
|
2071
|
+
const value = localStorage.getItem(STORAGE_KEY);
|
|
2072
|
+
return isTheme(value) ? value : null;
|
|
2073
|
+
} catch {
|
|
2074
|
+
return null;
|
|
1955
2075
|
}
|
|
1956
|
-
return ["L"];
|
|
1957
2076
|
}
|
|
1958
|
-
function
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
if (Number.isFinite(n) && n > 0) return n;
|
|
2077
|
+
function writeStoredTheme(theme) {
|
|
2078
|
+
try {
|
|
2079
|
+
localStorage.setItem(STORAGE_KEY, theme);
|
|
2080
|
+
} catch {
|
|
1963
2081
|
}
|
|
1964
|
-
|
|
2082
|
+
}
|
|
2083
|
+
function noop() {
|
|
1965
2084
|
}
|
|
1966
2085
|
|
|
1967
|
-
// src/
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
2086
|
+
// src/app/mount-grist-widget.tsx
|
|
2087
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2088
|
+
function entryProps(entry, dev = false) {
|
|
2089
|
+
const widget = isGristWidget(entry.widget) ? entry.widget : void 0;
|
|
2090
|
+
const options = widget?.options ?? entry.GRIST_OPTIONS;
|
|
2091
|
+
if (!options) {
|
|
2092
|
+
throw new Error(
|
|
2093
|
+
"mountGristWidget: src/app/App.tsx exports neither `widget` (defineGristWidget) nor `GRIST_OPTIONS`."
|
|
2094
|
+
);
|
|
1973
2095
|
}
|
|
1974
|
-
|
|
2096
|
+
const props = {
|
|
2097
|
+
App: entry.default,
|
|
2098
|
+
options,
|
|
2099
|
+
metadata: widget?.metadata ?? entry.WIDGET_METADATA,
|
|
2100
|
+
dev
|
|
2101
|
+
};
|
|
2102
|
+
if ("Alerts" in entry) props.Alerts = entry.Alerts;
|
|
2103
|
+
if (entry.LocalDevPage) props.LocalDevPage = entry.LocalDevPage;
|
|
2104
|
+
if (entry.ReleasePage) props.ReleasePage = entry.ReleasePage;
|
|
2105
|
+
return props;
|
|
1975
2106
|
}
|
|
1976
|
-
function
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2107
|
+
function GristWidgetApp({
|
|
2108
|
+
App,
|
|
2109
|
+
options,
|
|
2110
|
+
metadata,
|
|
2111
|
+
dev = false,
|
|
2112
|
+
Alerts = WidgetAlerts,
|
|
2113
|
+
LocalDevPage: LocalDevPage2 = LocalDevPage,
|
|
2114
|
+
ReleasePage: ReleasePage2 = ReleasePage
|
|
2115
|
+
}) {
|
|
2116
|
+
const Wrap = Alerts ?? PassThrough;
|
|
2117
|
+
if (isEmbedded2()) {
|
|
2118
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(GristWidgetProvider, { options, children: [
|
|
2119
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PageMetadata, { metadata }),
|
|
2120
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EmbeddedThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Wrap, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react9.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(App, {}) }) }) }) }) })
|
|
2121
|
+
] });
|
|
1980
2122
|
}
|
|
1981
|
-
|
|
2123
|
+
const channel = parseDeployPath(window.location.pathname).channel;
|
|
2124
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(StandaloneThemeProvider, { children: [
|
|
2125
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PageMetadata, { metadata }),
|
|
2126
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: dev ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocalDevPage2, {}) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ReleasePage2, { channel }) })
|
|
2127
|
+
] });
|
|
1982
2128
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2129
|
+
var FILL = { minHeight: "100%", width: "100%" };
|
|
2130
|
+
function isEmbedded2() {
|
|
2131
|
+
return typeof window !== "undefined" && window.self !== window.top;
|
|
2132
|
+
}
|
|
2133
|
+
function PageMetadata({ metadata }) {
|
|
2134
|
+
useWidgetMetadata(metadata?.title ? { title: metadata.title, description: metadata.description ?? "" } : null);
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2137
|
+
function PassThrough({ children }) {
|
|
2138
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
|
|
1992
2139
|
}
|
|
1993
2140
|
|
|
1994
|
-
// src/emulator/
|
|
1995
|
-
function
|
|
2141
|
+
// src/emulator/testing/declared-document.ts
|
|
2142
|
+
function declaredColumns(options) {
|
|
2143
|
+
return (options.columns ?? []).map(
|
|
2144
|
+
(column) => typeof column === "string" ? { name: column, type: "Any", declared: void 0, allowMultiple: false } : { name: column.name, type: firstType(column.type), declared: column.type, allowMultiple: column.allowMultiple === true }
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
function firstType(type) {
|
|
2148
|
+
return (type ?? "Any").split(",")[0].trim() || "Any";
|
|
2149
|
+
}
|
|
2150
|
+
function mapEveryColumn(columns) {
|
|
2151
|
+
return Object.fromEntries(columns.map((c) => [c.name, c.allowMultiple ? [c.name] : c.name]));
|
|
2152
|
+
}
|
|
2153
|
+
function declaredDocument(columns, rows) {
|
|
2154
|
+
const given = rows ?? [Object.fromEntries(columns.map((c) => [c.name, sampleValue(c.type)]))];
|
|
1996
2155
|
return {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
if (tableId === "_grist_Tables_column") {
|
|
2008
|
-
return synthesizeTablesColumnMeta(state);
|
|
2009
|
-
}
|
|
2010
|
-
const table = getTable(state, tableId);
|
|
2011
|
-
return rowsToColumnar(table, table.rows);
|
|
2012
|
-
},
|
|
2013
|
-
async applyUserActions(actions) {
|
|
2014
|
-
const result = applyUserActions(state, bus, actions);
|
|
2015
|
-
return { retValues: result.retValues };
|
|
2016
|
-
},
|
|
2017
|
-
async getAccessToken() {
|
|
2018
|
-
if (!state.accessToken) {
|
|
2019
|
-
const err = new Error("getAccessToken not available (no token configured on emulator)");
|
|
2020
|
-
throw err;
|
|
2156
|
+
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
2157
|
+
docName: "Test document",
|
|
2158
|
+
mode: "schema+data",
|
|
2159
|
+
tables: {
|
|
2160
|
+
Table1: {
|
|
2161
|
+
columns: Object.fromEntries(columns.map((c) => [c.name, { type: c.type }])),
|
|
2162
|
+
rows: given.map((row, i) => ({
|
|
2163
|
+
id: typeof row.id === "number" ? row.id : i + 1,
|
|
2164
|
+
...Object.fromEntries(columns.map((c) => [c.name, encodeGristCell(c.declared, row[c.name] ?? null)]))
|
|
2165
|
+
}))
|
|
2021
2166
|
}
|
|
2022
|
-
return { ...state.accessToken };
|
|
2023
2167
|
}
|
|
2024
2168
|
};
|
|
2025
2169
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
return
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
return
|
|
2039
|
-
|
|
2040
|
-
async allowSelectBy() {
|
|
2041
|
-
return state.configuration?.allowSelectBy ?? false;
|
|
2042
|
-
},
|
|
2043
|
-
async setSelectedRows(rowIds) {
|
|
2044
|
-
if (!rowIds || rowIds.length === 0) {
|
|
2045
|
-
state.selection.rowId = null;
|
|
2046
|
-
} else {
|
|
2047
|
-
state.selection.rowId = rowIds[0];
|
|
2048
|
-
}
|
|
2049
|
-
bus.emit({
|
|
2050
|
-
type: "cursor-change",
|
|
2051
|
-
tableId: state.selection.tableId,
|
|
2052
|
-
rowId: state.selection.rowId
|
|
2053
|
-
});
|
|
2054
|
-
},
|
|
2055
|
-
async setCursorPos(cursor) {
|
|
2056
|
-
const tableId = state.selection.tableId;
|
|
2057
|
-
if (typeof cursor.rowId === "number") state.selection.rowId = cursor.rowId;
|
|
2058
|
-
state.selection.cursor = { ...state.selection.cursor, ...cursor };
|
|
2059
|
-
bus.emit({
|
|
2060
|
-
type: "cursor-change",
|
|
2061
|
-
tableId,
|
|
2062
|
-
rowId: state.selection.rowId,
|
|
2063
|
-
cursor: state.selection.cursor
|
|
2064
|
-
});
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2170
|
+
function sampleValue(type) {
|
|
2171
|
+
switch (type) {
|
|
2172
|
+
case "Numeric":
|
|
2173
|
+
case "Int":
|
|
2174
|
+
return 42;
|
|
2175
|
+
case "Bool":
|
|
2176
|
+
return true;
|
|
2177
|
+
case "Date":
|
|
2178
|
+
case "DateTime":
|
|
2179
|
+
return 1767225600;
|
|
2180
|
+
// 2026-01-01, as Grist stores it: epoch seconds
|
|
2181
|
+
default:
|
|
2182
|
+
return "Sample";
|
|
2183
|
+
}
|
|
2067
2184
|
}
|
|
2068
2185
|
|
|
2069
|
-
// src/emulator/api/
|
|
2070
|
-
function
|
|
2071
|
-
function emit() {
|
|
2072
|
-
bus.emit({
|
|
2073
|
-
type: "options-change",
|
|
2074
|
-
options: { ...state.widgetOptions },
|
|
2075
|
-
interaction: { ...state.widgetInteraction }
|
|
2076
|
-
});
|
|
2077
|
-
}
|
|
2186
|
+
// src/emulator/api/command-api.ts
|
|
2187
|
+
function createCommandApiImpl(bus) {
|
|
2078
2188
|
return {
|
|
2079
|
-
async
|
|
2080
|
-
|
|
2081
|
-
},
|
|
2082
|
-
async setOption(key, value) {
|
|
2083
|
-
state.widgetOptions[key] = value;
|
|
2084
|
-
emit();
|
|
2085
|
-
},
|
|
2086
|
-
async getOptions() {
|
|
2087
|
-
return Object.keys(state.widgetOptions).length > 0 ? { ...state.widgetOptions } : null;
|
|
2088
|
-
},
|
|
2089
|
-
// Replaces every option, as Grist's WidgetAPI.setOptions does.
|
|
2090
|
-
async setOptions(options) {
|
|
2091
|
-
state.widgetOptions = { ...options };
|
|
2092
|
-
emit();
|
|
2093
|
-
},
|
|
2094
|
-
async clearOptions() {
|
|
2095
|
-
state.widgetOptions = {};
|
|
2096
|
-
emit();
|
|
2189
|
+
async run(name) {
|
|
2190
|
+
bus.emit({ type: "user-action", action: ["RunCommand", name] });
|
|
2097
2191
|
}
|
|
2098
2192
|
};
|
|
2099
2193
|
}
|
|
2100
2194
|
|
|
2101
|
-
// src/emulator/
|
|
2102
|
-
function
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
CustomSectionAPI: createCustomSectionApiImpl(state, bus),
|
|
2108
|
-
GristAPI: createGristApiImpl(),
|
|
2109
|
-
CommandAPI: createCommandApiImpl(bus)
|
|
2110
|
-
};
|
|
2195
|
+
// src/emulator/core/mappings.ts
|
|
2196
|
+
function resolveMappings(state) {
|
|
2197
|
+
if (state.selection.mappings !== void 0 && state.selection.mappings !== null) {
|
|
2198
|
+
return state.selection.mappings;
|
|
2199
|
+
}
|
|
2200
|
+
return null;
|
|
2111
2201
|
}
|
|
2112
2202
|
|
|
2113
|
-
// src/emulator/
|
|
2114
|
-
function
|
|
2115
|
-
const limit = options.historyLimit ?? 200;
|
|
2116
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
2117
|
-
const history = [];
|
|
2203
|
+
// src/emulator/api/custom-section-api.ts
|
|
2204
|
+
function createCustomSectionApiImpl(state, bus) {
|
|
2118
2205
|
return {
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
} catch (err) {
|
|
2126
|
-
console.error("[grist-emulator] event listener threw:", err);
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
},
|
|
2130
|
-
on(listener) {
|
|
2131
|
-
listeners.add(listener);
|
|
2132
|
-
return () => {
|
|
2133
|
-
listeners.delete(listener);
|
|
2206
|
+
async configure(settings) {
|
|
2207
|
+
const config = {
|
|
2208
|
+
requiredAccess: settings.requiredAccess,
|
|
2209
|
+
columns: settings.columns,
|
|
2210
|
+
allowSelectBy: settings.allowSelectBy,
|
|
2211
|
+
hasCustomOptions: settings.hasCustomOptions
|
|
2134
2212
|
};
|
|
2213
|
+
state.configuration = config;
|
|
2214
|
+
bus.emit({ type: "ready", configuration: config });
|
|
2215
|
+
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
2135
2216
|
},
|
|
2136
|
-
|
|
2137
|
-
return
|
|
2138
|
-
},
|
|
2139
|
-
clear() {
|
|
2140
|
-
history.length = 0;
|
|
2217
|
+
async mappings() {
|
|
2218
|
+
return resolveMappings(state);
|
|
2141
2219
|
}
|
|
2142
2220
|
};
|
|
2143
2221
|
}
|
|
2144
2222
|
|
|
2145
|
-
// src/emulator/
|
|
2146
|
-
function
|
|
2223
|
+
// src/emulator/api/grist-api.ts
|
|
2224
|
+
function createGristApiImpl() {
|
|
2147
2225
|
return {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
colors: {}
|
|
2152
|
-
},
|
|
2153
|
-
...options?.fromReady ? { fromReady: true } : {}
|
|
2226
|
+
async ping() {
|
|
2227
|
+
return "pong";
|
|
2228
|
+
}
|
|
2154
2229
|
};
|
|
2155
2230
|
}
|
|
2156
2231
|
|
|
2157
|
-
// src/
|
|
2158
|
-
function
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
return ["BulkUpdateRecord", tableId, rowIds, columnarPatch];
|
|
2169
|
-
}
|
|
2170
|
-
function gristBulkAddRecordAction(tableId, values, rowIds) {
|
|
2171
|
-
const length = inferBulkLength(values);
|
|
2172
|
-
const ids = rowIds ?? new Array(length).fill(null);
|
|
2173
|
-
return ["BulkAddRecord", tableId, ids, values];
|
|
2174
|
-
}
|
|
2175
|
-
function gristBulkRemoveRecordAction(tableId, rowIds) {
|
|
2176
|
-
return ["BulkRemoveRecord", tableId, rowIds];
|
|
2177
|
-
}
|
|
2178
|
-
function inferBulkLength(values) {
|
|
2179
|
-
for (const arr of Object.values(values)) if (Array.isArray(arr)) return arr.length;
|
|
2180
|
-
return 0;
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
// src/emulator/transports/inline.ts
|
|
2184
|
-
function createInlineTransport(options) {
|
|
2185
|
-
const { state, bus, api } = options;
|
|
2186
|
-
const target = options.target ?? globalThis;
|
|
2187
|
-
installFakeIframeContext(target);
|
|
2188
|
-
const recordCbs = /* @__PURE__ */ new Map();
|
|
2189
|
-
const recordsCbs = /* @__PURE__ */ new Map();
|
|
2190
|
-
const optionsCbs = /* @__PURE__ */ new Set();
|
|
2191
|
-
const newRecordCbs = /* @__PURE__ */ new Set();
|
|
2192
|
-
const generic = /* @__PURE__ */ new Map();
|
|
2193
|
-
let lastRecord = null;
|
|
2194
|
-
let lastRecords = null;
|
|
2195
|
-
let lastOptions = null;
|
|
2196
|
-
let lastNewRecord = void 0;
|
|
2197
|
-
const buildInteraction = options.buildInteraction ?? (() => ({
|
|
2198
|
-
access_level: state.configuration?.requiredAccess ?? "read table",
|
|
2199
|
-
...state.widgetInteraction
|
|
2200
|
-
}));
|
|
2201
|
-
async function pushRecord(rowId) {
|
|
2202
|
-
const mappings = resolveMappings(state);
|
|
2203
|
-
if (rowId === "new") {
|
|
2204
|
-
lastNewRecord = mappings;
|
|
2205
|
-
for (const cb of newRecordCbs) {
|
|
2206
|
-
try {
|
|
2207
|
-
cb(mappings);
|
|
2208
|
-
} catch (err) {
|
|
2209
|
-
logErr(err);
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
if (rowId === null) {
|
|
2215
|
-
lastRecord = { record: null, mappings };
|
|
2216
|
-
for (const [cb] of recordCbs) {
|
|
2217
|
-
try {
|
|
2218
|
-
cb(null, mappings);
|
|
2219
|
-
} catch (err) {
|
|
2220
|
-
logErr(err);
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
return;
|
|
2232
|
+
// src/emulator/core/display-helpers.ts
|
|
2233
|
+
function inferDisplayHelpersByRef(columns) {
|
|
2234
|
+
const out = {};
|
|
2235
|
+
const helperColumns = Object.entries(columns).filter(
|
|
2236
|
+
([colId]) => colId.startsWith("gristHelper_")
|
|
2237
|
+
);
|
|
2238
|
+
const usedHelpers = /* @__PURE__ */ new Set();
|
|
2239
|
+
const colRefToColId = /* @__PURE__ */ new Map();
|
|
2240
|
+
for (const [colId, col] of Object.entries(columns)) {
|
|
2241
|
+
if (typeof col?.colRef === "number" && Number.isFinite(col.colRef)) {
|
|
2242
|
+
colRefToColId.set(col.colRef, colId);
|
|
2224
2243
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2244
|
+
}
|
|
2245
|
+
for (const [colId, col] of Object.entries(columns)) {
|
|
2246
|
+
const type = col?.type;
|
|
2247
|
+
if (typeof type !== "string" || !type.startsWith("Ref:") && !type.startsWith("RefList:")) {
|
|
2248
|
+
continue;
|
|
2230
2249
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
logErr(err);
|
|
2250
|
+
if (typeof col.displayColRef === "number" && Number.isFinite(col.displayColRef)) {
|
|
2251
|
+
const helperColId = colRefToColId.get(col.displayColRef);
|
|
2252
|
+
if (helperColId) {
|
|
2253
|
+
out[colId] = helperColId;
|
|
2254
|
+
usedHelpers.add(helperColId);
|
|
2255
|
+
continue;
|
|
2238
2256
|
}
|
|
2239
2257
|
}
|
|
2258
|
+
const match = helperColumns.find(([, helper]) => {
|
|
2259
|
+
const formula = helper?.formula;
|
|
2260
|
+
return typeof formula === "string" && (formula.includes(`$${colId}.`) || formula.includes(`$${colId}`));
|
|
2261
|
+
});
|
|
2262
|
+
if (match) {
|
|
2263
|
+
out[colId] = match[0];
|
|
2264
|
+
usedHelpers.add(match[0]);
|
|
2265
|
+
}
|
|
2240
2266
|
}
|
|
2241
|
-
|
|
2242
|
-
const
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2267
|
+
const unresolvedRefs = Object.entries(columns).filter(([colId, col]) => {
|
|
2268
|
+
const type = col?.type;
|
|
2269
|
+
return !out[colId] && typeof type === "string" && (type.startsWith("Ref:") || type.startsWith("RefList:"));
|
|
2270
|
+
}).sort((a, b) => (a[1]?.parentPos ?? Number.MAX_SAFE_INTEGER) - (b[1]?.parentPos ?? Number.MAX_SAFE_INTEGER));
|
|
2271
|
+
const helperByPos = helperColumns.filter(([helperId]) => !usedHelpers.has(helperId)).sort((a, b) => (a[1]?.parentPos ?? Number.MAX_SAFE_INTEGER) - (b[1]?.parentPos ?? Number.MAX_SAFE_INTEGER));
|
|
2272
|
+
for (const [refId, refCol] of unresolvedRefs) {
|
|
2273
|
+
const refPos = refCol?.parentPos ?? Number.MAX_SAFE_INTEGER;
|
|
2274
|
+
const candidate = helperByPos.find(([, helperCol]) => {
|
|
2275
|
+
const helperPos = helperCol?.parentPos ?? Number.MAX_SAFE_INTEGER;
|
|
2276
|
+
return helperPos > refPos;
|
|
2277
|
+
});
|
|
2278
|
+
if (!candidate) continue;
|
|
2279
|
+
out[refId] = candidate[0];
|
|
2280
|
+
usedHelpers.add(candidate[0]);
|
|
2281
|
+
}
|
|
2282
|
+
for (const [refId] of unresolvedRefs) {
|
|
2283
|
+
if (out[refId]) continue;
|
|
2284
|
+
const candidate = helperByPos.find(([helperId]) => !usedHelpers.has(helperId));
|
|
2285
|
+
if (!candidate) continue;
|
|
2286
|
+
out[refId] = candidate[0];
|
|
2287
|
+
usedHelpers.add(candidate[0]);
|
|
2288
|
+
}
|
|
2289
|
+
return out;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// src/emulator/core/document.ts
|
|
2293
|
+
var SYSTEM_COLUMN = "id";
|
|
2294
|
+
var EMULATOR_JS_FORMULA_PLACEHOLDER = "(emulator JavaScript formula)";
|
|
2295
|
+
function enrichEmulatorColumnMetadata(col) {
|
|
2296
|
+
const originalTrimmed = col.formula?.trim() ?? "";
|
|
2297
|
+
const explicitInferred = col.inferredFormula?.trim() ?? "";
|
|
2298
|
+
if (col.formulaFn) {
|
|
2299
|
+
const inferredFormula = explicitInferred || originalTrimmed || EMULATOR_JS_FORMULA_PLACEHOLDER;
|
|
2300
|
+
return {
|
|
2301
|
+
...col,
|
|
2302
|
+
...originalTrimmed ? { formula: col.formula } : {},
|
|
2303
|
+
inferredFormula
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
if (explicitInferred || originalTrimmed) {
|
|
2307
|
+
return {
|
|
2308
|
+
...col,
|
|
2309
|
+
inferredFormula: explicitInferred || originalTrimmed || void 0
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
return col;
|
|
2313
|
+
}
|
|
2314
|
+
function sortEmulatorColumnsByParentPos(columns) {
|
|
2315
|
+
const entries = Object.entries(columns);
|
|
2316
|
+
entries.sort(([ka, a], [kb, b]) => {
|
|
2317
|
+
const pa = a.parentPos;
|
|
2318
|
+
const pb = b.parentPos;
|
|
2319
|
+
if (pa !== void 0 && pb !== void 0 && pa !== pb) return pa - pb;
|
|
2320
|
+
if (pa !== void 0 && pb === void 0) return -1;
|
|
2321
|
+
if (pa === void 0 && pb !== void 0) return 1;
|
|
2322
|
+
return ka.localeCompare(kb);
|
|
2323
|
+
});
|
|
2324
|
+
return Object.fromEntries(entries);
|
|
2325
|
+
}
|
|
2326
|
+
function inferColumnsFromRows(rows) {
|
|
2327
|
+
const columns = {};
|
|
2328
|
+
for (const row of rows) {
|
|
2329
|
+
for (const key of Object.keys(row)) {
|
|
2330
|
+
if (key === SYSTEM_COLUMN) continue;
|
|
2331
|
+
if (!columns[key]) columns[key] = inferColumnType(row[key]);
|
|
2259
2332
|
}
|
|
2260
2333
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2334
|
+
return columns;
|
|
2335
|
+
}
|
|
2336
|
+
function inferColumnType(sample) {
|
|
2337
|
+
switch (typeof sample) {
|
|
2338
|
+
case "number":
|
|
2339
|
+
return { type: "Numeric" };
|
|
2340
|
+
case "boolean":
|
|
2341
|
+
return { type: "Bool" };
|
|
2342
|
+
case "string":
|
|
2343
|
+
return { type: "Text" };
|
|
2344
|
+
default:
|
|
2345
|
+
return { type: "Any" };
|
|
2268
2346
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2347
|
+
}
|
|
2348
|
+
function buildTable(tableId, input) {
|
|
2349
|
+
const rows = input.rows ?? [];
|
|
2350
|
+
const inferred = inferColumnsFromRows(rows);
|
|
2351
|
+
const merged = {};
|
|
2352
|
+
for (const [colId, col] of Object.entries({ ...inferred, ...input.columns ?? {} })) {
|
|
2353
|
+
merged[colId] = enrichEmulatorColumnMetadata(col);
|
|
2354
|
+
}
|
|
2355
|
+
const columns = sortEmulatorColumnsByParentPos(merged);
|
|
2356
|
+
return {
|
|
2357
|
+
tableId,
|
|
2358
|
+
label: input.label,
|
|
2359
|
+
columns,
|
|
2360
|
+
rows: rows.map((row) => ({ ...row }))
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
function createDocument(input) {
|
|
2364
|
+
if (!input.tables || Object.keys(input.tables).length === 0) {
|
|
2365
|
+
throw new Error("createDocument: at least one table is required");
|
|
2366
|
+
}
|
|
2367
|
+
const normalized = normalizeReplicaDocumentInput(input);
|
|
2368
|
+
const tables = /* @__PURE__ */ new Map();
|
|
2369
|
+
for (const [tableId, tableInput] of Object.entries(normalized.tables)) {
|
|
2370
|
+
tables.set(tableId, buildTable(tableId, tableInput));
|
|
2371
|
+
}
|
|
2372
|
+
const firstTableId = tables.keys().next().value;
|
|
2373
|
+
const requestedTableId = normalized.selection?.tableId;
|
|
2374
|
+
const selectedTableId = requestedTableId && tables.has(requestedTableId) ? requestedTableId : firstTableId;
|
|
2375
|
+
const selectedTable = tables.get(selectedTableId);
|
|
2376
|
+
const fallbackRowId = typeof selectedTable.rows[0]?.id === "number" ? selectedTable.rows[0].id : null;
|
|
2377
|
+
const selection = {
|
|
2378
|
+
tableId: selectedTableId,
|
|
2379
|
+
rowId: normalized.selection?.rowId ?? fallbackRowId,
|
|
2380
|
+
mappings: normalized.selection?.mappings,
|
|
2381
|
+
cursor: normalized.selection?.cursor
|
|
2382
|
+
};
|
|
2383
|
+
return {
|
|
2384
|
+
docName: normalized.docName ?? "Untitled emulator document",
|
|
2385
|
+
tables,
|
|
2386
|
+
selection,
|
|
2387
|
+
widgetOptions: { ...normalized.widgetOptions ?? {} },
|
|
2388
|
+
widgetInteraction: { ...normalized.widgetInteraction ?? {} },
|
|
2389
|
+
configuration: normalized.configuration ?? null,
|
|
2390
|
+
accessToken: normalized.accessToken ?? null
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
function getTable(state, tableId) {
|
|
2394
|
+
const table = state.tables.get(tableId);
|
|
2395
|
+
if (!table) throw new Error(`Unknown table: ${tableId}`);
|
|
2396
|
+
return table;
|
|
2397
|
+
}
|
|
2398
|
+
function getSelectedTable(state) {
|
|
2399
|
+
return getTable(state, state.selection.tableId);
|
|
2400
|
+
}
|
|
2401
|
+
function nextRowId(table) {
|
|
2402
|
+
let max = 0;
|
|
2403
|
+
for (const row of table.rows) {
|
|
2404
|
+
if (typeof row.id === "number" && row.id > max) max = row.id;
|
|
2405
|
+
}
|
|
2406
|
+
return max + 1;
|
|
2407
|
+
}
|
|
2408
|
+
function findRow(table, rowId) {
|
|
2409
|
+
return table.rows.find((r) => r.id === rowId);
|
|
2410
|
+
}
|
|
2411
|
+
function findRowIndex(table, rowId) {
|
|
2412
|
+
return table.rows.findIndex((r) => r.id === rowId);
|
|
2413
|
+
}
|
|
2414
|
+
function evaluateRowFormulas(state, table, row) {
|
|
2415
|
+
for (const [colId, col] of Object.entries(table.columns)) {
|
|
2416
|
+
if (col.formulaFn) {
|
|
2274
2417
|
try {
|
|
2275
|
-
|
|
2418
|
+
row[colId] = col.formulaFn(row, { table, document: state });
|
|
2276
2419
|
} catch (err) {
|
|
2277
|
-
|
|
2420
|
+
row[colId] = formulaError2(err);
|
|
2278
2421
|
}
|
|
2279
2422
|
}
|
|
2280
2423
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
pushOptions();
|
|
2295
|
-
if (options.initialTheme) {
|
|
2296
|
-
notify("message", buildGristThemeHostMessage(options.initialTheme, { fromReady: true }));
|
|
2297
|
-
}
|
|
2298
|
-
await pushRecords();
|
|
2299
|
-
await pushRecord(state.selection.rowId);
|
|
2300
|
-
break;
|
|
2301
|
-
case "mappings-change":
|
|
2302
|
-
await pushRecords();
|
|
2303
|
-
await pushRecord(state.selection.rowId);
|
|
2304
|
-
break;
|
|
2305
|
-
case "theme-change":
|
|
2306
|
-
notify("message", event.message);
|
|
2307
|
-
break;
|
|
2424
|
+
}
|
|
2425
|
+
function escapeRegExp(s) {
|
|
2426
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2427
|
+
}
|
|
2428
|
+
function extractRefListIdsFromWire(raw) {
|
|
2429
|
+
if (raw == null) return [];
|
|
2430
|
+
if (!Array.isArray(raw)) return [];
|
|
2431
|
+
const items = raw[0] === "L" ? raw.slice(1) : raw;
|
|
2432
|
+
const out = [];
|
|
2433
|
+
for (const item of items) {
|
|
2434
|
+
if (typeof item === "number" && Number.isFinite(item) && item > 0) {
|
|
2435
|
+
out.push(Math.trunc(item));
|
|
2436
|
+
continue;
|
|
2308
2437
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
},
|
|
2315
|
-
async create(records, options2) {
|
|
2316
|
-
const list = Array.isArray(records) ? records : [records];
|
|
2317
|
-
const ids = [];
|
|
2318
|
-
for (const r of list) {
|
|
2319
|
-
const result = applyUserActions(state, bus, [
|
|
2320
|
-
gristAddRecordAction(tableId, parseFields(r.fields, options2))
|
|
2321
|
-
]);
|
|
2322
|
-
ids.push({ id: result.retValues[0] });
|
|
2323
|
-
}
|
|
2324
|
-
return Array.isArray(records) ? ids : ids[0];
|
|
2325
|
-
},
|
|
2326
|
-
async update(records, options2) {
|
|
2327
|
-
const list = Array.isArray(records) ? records : [records];
|
|
2328
|
-
const actions = list.map((r) => gristUpdateRecordAction(tableId, r.id, parseFields(r.fields, options2)));
|
|
2329
|
-
applyUserActions(state, bus, actions);
|
|
2330
|
-
},
|
|
2331
|
-
async upsert(records, options2) {
|
|
2332
|
-
const list = Array.isArray(records) ? records : [records];
|
|
2333
|
-
const allowAdd = options2?.add !== false;
|
|
2334
|
-
const allowUpdate = options2?.update !== false;
|
|
2335
|
-
const table = getTable(state, tableId);
|
|
2336
|
-
const actions = [];
|
|
2337
|
-
for (const r of list) {
|
|
2338
|
-
const matches = table.rows.filter(
|
|
2339
|
-
(row) => Object.entries(r.require).every(([k, v]) => row[k] === v)
|
|
2340
|
-
);
|
|
2341
|
-
if (matches.length > 0) {
|
|
2342
|
-
if (!allowUpdate) continue;
|
|
2343
|
-
const fields = parseFields(r.fields ?? {}, options2);
|
|
2344
|
-
for (const m of matches) actions.push(gristUpdateRecordAction(tableId, m.id, fields));
|
|
2345
|
-
} else if (allowAdd) {
|
|
2346
|
-
actions.push(gristAddRecordAction(tableId, { ...r.require, ...parseFields(r.fields ?? {}, options2) }));
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
if (actions.length > 0) applyUserActions(state, bus, actions);
|
|
2350
|
-
},
|
|
2351
|
-
async destroy(recordIds) {
|
|
2352
|
-
if (Array.isArray(recordIds)) {
|
|
2353
|
-
applyUserActions(state, bus, [gristBulkRemoveRecordAction(tableId, recordIds)]);
|
|
2354
|
-
} else {
|
|
2355
|
-
applyUserActions(state, bus, [gristRemoveRecordAction(tableId, recordIds)]);
|
|
2356
|
-
}
|
|
2357
|
-
void gristBulkAddRecordAction;
|
|
2358
|
-
void gristBulkUpdateRecordAction;
|
|
2359
|
-
}
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
function notify(event, ...args) {
|
|
2363
|
-
const set = generic.get(event);
|
|
2364
|
-
if (!set) return;
|
|
2365
|
-
for (const fn of set) {
|
|
2366
|
-
try {
|
|
2367
|
-
fn(...args);
|
|
2368
|
-
} catch (err) {
|
|
2369
|
-
logErr(err);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
const grist2 = {
|
|
2374
|
-
async ready(opts) {
|
|
2375
|
-
await api.CustomSectionAPI.configure(opts ? { ...opts, onEditOptions: void 0 } : {});
|
|
2376
|
-
},
|
|
2377
|
-
onRecord(cb, opts) {
|
|
2378
|
-
recordCbs.set(cb, opts);
|
|
2379
|
-
if (lastRecord) {
|
|
2380
|
-
try {
|
|
2381
|
-
cb(lastRecord.record, lastRecord.mappings);
|
|
2382
|
-
} catch (err) {
|
|
2383
|
-
logErr(err);
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
},
|
|
2387
|
-
onRecords(cb, opts) {
|
|
2388
|
-
recordsCbs.set(cb, opts);
|
|
2389
|
-
if (lastRecords) {
|
|
2390
|
-
try {
|
|
2391
|
-
cb(lastRecords.records, lastRecords.mappings);
|
|
2392
|
-
} catch (err) {
|
|
2393
|
-
logErr(err);
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
},
|
|
2397
|
-
onOptions(cb) {
|
|
2398
|
-
optionsCbs.add(cb);
|
|
2399
|
-
if (lastOptions) {
|
|
2400
|
-
try {
|
|
2401
|
-
cb(lastOptions.options, lastOptions.interaction);
|
|
2402
|
-
} catch (err) {
|
|
2403
|
-
logErr(err);
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
},
|
|
2407
|
-
onNewRecord(cb) {
|
|
2408
|
-
newRecordCbs.add(cb);
|
|
2409
|
-
if (lastNewRecord !== void 0) {
|
|
2410
|
-
try {
|
|
2411
|
-
cb(lastNewRecord);
|
|
2412
|
-
} catch (err) {
|
|
2413
|
-
logErr(err);
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
},
|
|
2417
|
-
mapColumnNames(data) {
|
|
2418
|
-
const mappings = resolveMappings(state);
|
|
2419
|
-
if (!mappings) return null;
|
|
2420
|
-
const out = {};
|
|
2421
|
-
let allMapped = true;
|
|
2422
|
-
for (const [logical, real] of Object.entries(mappings)) {
|
|
2423
|
-
if (Array.isArray(real)) {
|
|
2424
|
-
out[logical] = real.map((r) => data[r]);
|
|
2425
|
-
} else if (typeof real === "string") {
|
|
2426
|
-
out[logical] = data[real];
|
|
2427
|
-
} else {
|
|
2428
|
-
allMapped = false;
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
return allMapped ? out : null;
|
|
2432
|
-
},
|
|
2433
|
-
mapColumnNamesBack(data) {
|
|
2434
|
-
const mappings = resolveMappings(state);
|
|
2435
|
-
if (!mappings) return data;
|
|
2436
|
-
const out = {};
|
|
2437
|
-
for (const [logical, real] of Object.entries(mappings)) {
|
|
2438
|
-
if (typeof real === "string" && logical in data) {
|
|
2439
|
-
out[real] = data[logical];
|
|
2440
|
-
}
|
|
2438
|
+
if (item && typeof item === "object" && "rowId" in item) {
|
|
2439
|
+
const r = item.rowId;
|
|
2440
|
+
if (typeof r === "number" && Number.isFinite(r) && r > 0) {
|
|
2441
|
+
out.push(Math.trunc(r));
|
|
2442
|
+
continue;
|
|
2441
2443
|
}
|
|
2442
|
-
return out;
|
|
2443
|
-
},
|
|
2444
|
-
getOption: (k) => api.WidgetAPI.getOption(k),
|
|
2445
|
-
setOption: (k, v) => api.WidgetAPI.setOption(k, v),
|
|
2446
|
-
getOptions: () => api.WidgetAPI.getOptions(),
|
|
2447
|
-
setOptions: (o) => api.WidgetAPI.setOptions(o),
|
|
2448
|
-
clearOptions: () => api.WidgetAPI.clearOptions(),
|
|
2449
|
-
setCursorPos: (pos) => api.GristView.setCursorPos(pos),
|
|
2450
|
-
setSelectedRows: (rowIds) => api.GristView.setSelectedRows(rowIds),
|
|
2451
|
-
fetchSelectedTable: (opts) => fetchSelectedTableShaped(opts),
|
|
2452
|
-
fetchSelectedRecord: (rowId, opts) => api.GristView.fetchSelectedRecord(rowId, coerceFetchOptions(opts)),
|
|
2453
|
-
docApi: {
|
|
2454
|
-
getDocName: () => api.GristDocAPI.getDocName(),
|
|
2455
|
-
listTables: () => api.GristDocAPI.listTables(),
|
|
2456
|
-
fetchTable: (id) => api.GristDocAPI.fetchTable(id),
|
|
2457
|
-
applyUserActions: (actions, options2) => api.GristDocAPI.applyUserActions(actions, options2),
|
|
2458
|
-
getAccessToken: (opts) => api.GristDocAPI.getAccessToken(opts),
|
|
2459
|
-
fetchSelectedTable: (opts) => fetchSelectedTableShaped(opts),
|
|
2460
|
-
fetchSelectedRecord: (rowId, opts) => api.GristView.fetchSelectedRecord(rowId, coerceFetchOptions(opts))
|
|
2461
|
-
},
|
|
2462
|
-
get selectedTable() {
|
|
2463
|
-
return makeTableOps(state.selection.tableId);
|
|
2464
|
-
},
|
|
2465
|
-
getTable(tableId) {
|
|
2466
|
-
return makeTableOps(tableId ?? state.selection.tableId);
|
|
2467
|
-
},
|
|
2468
|
-
sectionApi: {
|
|
2469
|
-
configure: (settings) => api.CustomSectionAPI.configure(settings),
|
|
2470
|
-
mappings: () => api.CustomSectionAPI.mappings()
|
|
2471
|
-
},
|
|
2472
|
-
on(eventName, listener) {
|
|
2473
|
-
let set = generic.get(eventName);
|
|
2474
|
-
if (!set) generic.set(eventName, set = /* @__PURE__ */ new Set());
|
|
2475
|
-
set.add(listener);
|
|
2476
|
-
return () => set.delete(listener);
|
|
2477
|
-
}
|
|
2478
|
-
};
|
|
2479
|
-
target.grist = grist2;
|
|
2480
|
-
return {
|
|
2481
|
-
grist: grist2,
|
|
2482
|
-
dispose() {
|
|
2483
|
-
offBus();
|
|
2484
|
-
const t = target;
|
|
2485
|
-
if (t.grist === grist2) delete t.grist;
|
|
2486
|
-
recordCbs.clear();
|
|
2487
|
-
recordsCbs.clear();
|
|
2488
|
-
optionsCbs.clear();
|
|
2489
|
-
newRecordCbs.clear();
|
|
2490
|
-
generic.clear();
|
|
2491
|
-
lastRecord = null;
|
|
2492
|
-
lastRecords = null;
|
|
2493
|
-
lastOptions = null;
|
|
2494
|
-
lastNewRecord = void 0;
|
|
2495
|
-
},
|
|
2496
|
-
fireDataChange(tableId) {
|
|
2497
|
-
bus.emit({ type: "data-change", tableId: tableId ?? state.selection.tableId });
|
|
2498
|
-
},
|
|
2499
|
-
fireCursor(rowId) {
|
|
2500
|
-
const id = rowId === void 0 ? state.selection.rowId : rowId;
|
|
2501
|
-
bus.emit({ type: "cursor-change", tableId: state.selection.tableId, rowId: id });
|
|
2502
|
-
},
|
|
2503
|
-
fireOptions() {
|
|
2504
|
-
pushOptions();
|
|
2505
|
-
},
|
|
2506
|
-
fireTheme(message2) {
|
|
2507
|
-
bus.emit({ type: "theme-change", message: message2 });
|
|
2508
|
-
},
|
|
2509
|
-
emitInitialState() {
|
|
2510
|
-
pushOptions();
|
|
2511
|
-
void pushRecords().then(() => pushRecord(state.selection.rowId));
|
|
2512
2444
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
function columnsToRows(columnar) {
|
|
2516
|
-
const cols = Object.keys(columnar);
|
|
2517
|
-
if (cols.length === 0) return [];
|
|
2518
|
-
const length = (columnar[cols[0]] ?? []).length;
|
|
2519
|
-
const rows = [];
|
|
2520
|
-
for (let i = 0; i < length; i++) {
|
|
2521
|
-
const row = {};
|
|
2522
|
-
for (const c of cols) row[c] = columnar[c][i];
|
|
2523
|
-
rows.push(row);
|
|
2445
|
+
const t = coerceGristDocApiRefRowId(item);
|
|
2446
|
+
if (t !== void 0 && t > 0) out.push(Math.trunc(t));
|
|
2524
2447
|
}
|
|
2525
|
-
return
|
|
2448
|
+
return Array.from(new Set(out));
|
|
2526
2449
|
}
|
|
2527
|
-
function
|
|
2528
|
-
|
|
2450
|
+
function fieldFromRefListHelperFormula(formula, refColId) {
|
|
2451
|
+
if (!formula) return null;
|
|
2452
|
+
const re = new RegExp(`\\$${escapeRegExp(refColId)}\\.([A-Za-z_]\\w*)`);
|
|
2453
|
+
const m = formula.match(re);
|
|
2454
|
+
return m?.[1] ?? null;
|
|
2529
2455
|
}
|
|
2530
|
-
function
|
|
2531
|
-
|
|
2532
|
-
const
|
|
2533
|
-
|
|
2456
|
+
function firstTextLikeColumnId(columns) {
|
|
2457
|
+
let best = null;
|
|
2458
|
+
for (const [colId, col] of Object.entries(columns)) {
|
|
2459
|
+
if (colId === "id" || colId.startsWith("gristHelper_")) continue;
|
|
2460
|
+
const t = col.type;
|
|
2461
|
+
if (t !== "Text" && t !== "Choice") continue;
|
|
2462
|
+
const pos = col.parentPos ?? 99999;
|
|
2463
|
+
if (!best || pos < best.pos) best = { id: colId, pos };
|
|
2464
|
+
}
|
|
2465
|
+
return best?.id ?? null;
|
|
2534
2466
|
}
|
|
2535
|
-
function
|
|
2536
|
-
|
|
2467
|
+
function formatLabelFromCell(value) {
|
|
2468
|
+
if (value == null) return "";
|
|
2469
|
+
if (typeof value === "string") return value.trim();
|
|
2470
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2471
|
+
if (value instanceof Date) return value.toISOString();
|
|
2472
|
+
if (Array.isArray(value) && value[0] === "E") return "";
|
|
2473
|
+
if (typeof value === "object" && value && "__ref" in value) {
|
|
2474
|
+
const r = value.rowId;
|
|
2475
|
+
return typeof r === "number" && Number.isFinite(r) ? String(r) : "";
|
|
2476
|
+
}
|
|
2477
|
+
return "";
|
|
2537
2478
|
}
|
|
2538
|
-
function
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2479
|
+
function syncRefListDisplayHelpers(state, table, row) {
|
|
2480
|
+
const displayHelpersByRef = inferDisplayHelpersByRef(table.columns);
|
|
2481
|
+
for (const [colId, col] of Object.entries(table.columns)) {
|
|
2482
|
+
const type = col.type;
|
|
2483
|
+
if (typeof type !== "string" || !type.startsWith("RefList:")) continue;
|
|
2484
|
+
const helperColId = displayHelpersByRef[colId];
|
|
2485
|
+
if (!helperColId) continue;
|
|
2486
|
+
const helperMeta = table.columns[helperColId];
|
|
2487
|
+
if (!helperMeta || helperMeta.formulaFn) continue;
|
|
2488
|
+
const targetTableId = type.slice("RefList:".length);
|
|
2489
|
+
const targetTable = state.tables.get(targetTableId);
|
|
2490
|
+
if (!targetTable) continue;
|
|
2491
|
+
const ids = extractRefListIdsFromWire(row[colId]);
|
|
2492
|
+
if (ids.length === 0) {
|
|
2493
|
+
row[helperColId] = [];
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
const fieldId = fieldFromRefListHelperFormula(helperMeta.formula, colId) ?? firstTextLikeColumnId(targetTable.columns);
|
|
2497
|
+
const labels = ids.map((rid) => {
|
|
2498
|
+
if (!fieldId) return String(rid);
|
|
2499
|
+
const trow = findRow(targetTable, rid);
|
|
2500
|
+
if (!trow) return String(rid);
|
|
2501
|
+
const lbl = formatLabelFromCell(trow[fieldId]);
|
|
2502
|
+
return lbl !== "" ? lbl : String(rid);
|
|
2544
2503
|
});
|
|
2545
|
-
|
|
2504
|
+
row[helperColId] = labels;
|
|
2546
2505
|
}
|
|
2547
2506
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
var emulatorRowSchema = import_zod.z.object({ id: import_zod.z.number() }).catchall(import_zod.z.unknown());
|
|
2552
|
-
var formulaKindSchema = import_zod.z.enum(["computed", "trigger", "none", "empty_slot"]);
|
|
2553
|
-
var replicaColumnSchema = import_zod.z.object({
|
|
2554
|
-
colRef: import_zod.z.number().optional(),
|
|
2555
|
-
type: import_zod.z.string().optional(),
|
|
2556
|
-
label: import_zod.z.string().optional(),
|
|
2557
|
-
parentPos: import_zod.z.number().optional(),
|
|
2558
|
-
isFormula: import_zod.z.boolean().optional(),
|
|
2559
|
-
formulaKind: formulaKindSchema.optional(),
|
|
2560
|
-
formula: import_zod.z.string().optional(),
|
|
2561
|
-
description: import_zod.z.string().optional(),
|
|
2562
|
-
widgetOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
2563
|
-
displayColRef: import_zod.z.number().optional(),
|
|
2564
|
-
visibleColRef: import_zod.z.number().optional()
|
|
2565
|
-
}).strict();
|
|
2566
|
-
var replicaTableSchema = import_zod.z.object({
|
|
2567
|
-
label: import_zod.z.string().optional(),
|
|
2568
|
-
columns: import_zod.z.record(import_zod.z.string(), replicaColumnSchema).optional(),
|
|
2569
|
-
rows: import_zod.z.array(emulatorRowSchema).optional(),
|
|
2570
|
-
rowCount: import_zod.z.number().optional(),
|
|
2571
|
-
dataOmitted: import_zod.z.boolean().optional(),
|
|
2572
|
-
dataOmittedReason: import_zod.z.string().optional(),
|
|
2573
|
-
error: import_zod.z.string().optional()
|
|
2574
|
-
}).strict();
|
|
2575
|
-
var replicaSelectionSchema = import_zod.z.object({
|
|
2576
|
-
tableId: import_zod.z.string().optional(),
|
|
2577
|
-
rowId: import_zod.z.union([import_zod.z.number(), import_zod.z.literal("new"), import_zod.z.null()]).optional(),
|
|
2578
|
-
mappings: import_zod.z.unknown().nullable().optional(),
|
|
2579
|
-
cursor: import_zod.z.unknown().optional()
|
|
2580
|
-
}).strict();
|
|
2581
|
-
var replicaWidgetConfigurationSchema = import_zod.z.object({
|
|
2582
|
-
requiredAccess: import_zod.z.string().optional(),
|
|
2583
|
-
columns: import_zod.z.unknown().optional(),
|
|
2584
|
-
allowSelectBy: import_zod.z.boolean().optional(),
|
|
2585
|
-
hasCustomOptions: import_zod.z.boolean().optional()
|
|
2586
|
-
}).strict();
|
|
2587
|
-
var replicaAccessTokenSchema = import_zod.z.object({
|
|
2588
|
-
token: import_zod.z.string(),
|
|
2589
|
-
baseUrl: import_zod.z.string(),
|
|
2590
|
-
ttlMsecs: import_zod.z.number()
|
|
2591
|
-
}).strict();
|
|
2592
|
-
var replicaDocumentSchema = import_zod.z.object({
|
|
2593
|
-
generatedAt: import_zod.z.string(),
|
|
2594
|
-
source: import_zod.z.string().optional(),
|
|
2595
|
-
docName: import_zod.z.string().optional(),
|
|
2596
|
-
mode: import_zod.z.enum(["schema-only", "schema+samples", "schema+data"]).optional(),
|
|
2597
|
-
tables: import_zod.z.record(import_zod.z.string(), replicaTableSchema),
|
|
2598
|
-
selection: replicaSelectionSchema.optional(),
|
|
2599
|
-
widgetOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
2600
|
-
widgetInteraction: import_zod.z.unknown().optional(),
|
|
2601
|
-
configuration: replicaWidgetConfigurationSchema.optional(),
|
|
2602
|
-
accessToken: replicaAccessTokenSchema.nullable().optional()
|
|
2603
|
-
}).strict().refine((value) => Object.keys(value.tables).length > 0, {
|
|
2604
|
-
message: "at least one table is required",
|
|
2605
|
-
path: ["tables"]
|
|
2606
|
-
});
|
|
2607
|
-
function formatZodError(prefix, error) {
|
|
2608
|
-
const details = error.issues.map((issue) => {
|
|
2609
|
-
const where = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
2610
|
-
return `${where}: ${issue.message}`;
|
|
2611
|
-
}).join("; ");
|
|
2612
|
-
return new Error(`${prefix}: ${details}`);
|
|
2613
|
-
}
|
|
2614
|
-
function safeParseDocument(input) {
|
|
2615
|
-
return replicaDocumentSchema.safeParse(input);
|
|
2507
|
+
function formulaError2(err) {
|
|
2508
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
2509
|
+
return ["E", "Error", message2];
|
|
2616
2510
|
}
|
|
2617
|
-
function
|
|
2618
|
-
const
|
|
2619
|
-
|
|
2620
|
-
|
|
2511
|
+
function synthesizeTablesMeta(state) {
|
|
2512
|
+
const ids = [];
|
|
2513
|
+
const tableIds = [];
|
|
2514
|
+
const labels = [];
|
|
2515
|
+
let nextId = 1;
|
|
2516
|
+
for (const [tableId, table] of state.tables.entries()) {
|
|
2517
|
+
ids.push(nextId);
|
|
2518
|
+
tableIds.push(tableId);
|
|
2519
|
+
labels.push(table.label ?? null);
|
|
2520
|
+
nextId += 1;
|
|
2621
2521
|
}
|
|
2622
|
-
return
|
|
2522
|
+
return {
|
|
2523
|
+
id: ids,
|
|
2524
|
+
tableId: tableIds,
|
|
2525
|
+
// Optional human-readable label; real Grist keeps more fields, but these
|
|
2526
|
+
// are sufficient for `buildReplicaDocumentFromDocApi` to resolve table ids.
|
|
2527
|
+
label: labels
|
|
2528
|
+
};
|
|
2623
2529
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
const
|
|
2628
|
-
const
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
const
|
|
2636
|
-
const
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
const transport = inline;
|
|
2645
|
-
const grist2 = inline.grist;
|
|
2646
|
-
function apply(actions) {
|
|
2647
|
-
return applyUserActions(state, bus, actions);
|
|
2530
|
+
function synthesizeTablesColumnMeta(state) {
|
|
2531
|
+
const parentIds = [];
|
|
2532
|
+
const colIds = [];
|
|
2533
|
+
const columnIds = [];
|
|
2534
|
+
const types = [];
|
|
2535
|
+
const labels = [];
|
|
2536
|
+
const parentPos = [];
|
|
2537
|
+
const isFormula = [];
|
|
2538
|
+
const formulas = [];
|
|
2539
|
+
const descriptions = [];
|
|
2540
|
+
const widgetOptions = [];
|
|
2541
|
+
const displayCol = [];
|
|
2542
|
+
const visibleCol = [];
|
|
2543
|
+
const tableIdToInternalId = /* @__PURE__ */ new Map();
|
|
2544
|
+
{
|
|
2545
|
+
let nextId = 1;
|
|
2546
|
+
for (const [tableId] of state.tables.entries()) {
|
|
2547
|
+
tableIdToInternalId.set(tableId, nextId);
|
|
2548
|
+
nextId += 1;
|
|
2549
|
+
}
|
|
2648
2550
|
}
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
bus.emit({ type: "cursor-change", tableId: state.selection.tableId, rowId });
|
|
2668
|
-
},
|
|
2669
|
-
selectTable(tableId) {
|
|
2670
|
-
if (!state.tables.has(tableId)) throw new Error(`Unknown table: ${tableId}`);
|
|
2671
|
-
state.selection.tableId = tableId;
|
|
2672
|
-
const first = state.tables.get(tableId).rows[0]?.id ?? null;
|
|
2673
|
-
state.selection.rowId = first;
|
|
2674
|
-
bus.emit({ type: "data-change", tableId });
|
|
2675
|
-
bus.emit({ type: "cursor-change", tableId, rowId: first });
|
|
2676
|
-
},
|
|
2677
|
-
setColumnMappings(mappings) {
|
|
2678
|
-
if (mappings === void 0) {
|
|
2679
|
-
delete state.selection.mappings;
|
|
2680
|
-
} else {
|
|
2681
|
-
state.selection.mappings = mappings;
|
|
2682
|
-
}
|
|
2683
|
-
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
2684
|
-
},
|
|
2685
|
-
async setOptions(opts) {
|
|
2686
|
-
await api.WidgetAPI.setOptions(opts);
|
|
2687
|
-
},
|
|
2688
|
-
setInteraction(interaction) {
|
|
2689
|
-
state.widgetInteraction = { ...interaction };
|
|
2690
|
-
bus.emit({ type: "options-change", options: { ...state.widgetOptions }, interaction: { ...interaction } });
|
|
2691
|
-
},
|
|
2692
|
-
theme: {
|
|
2693
|
-
set(next) {
|
|
2694
|
-
bus.emit({ type: "theme-change", message: buildGristThemeHostMessage(next) });
|
|
2695
|
-
}
|
|
2696
|
-
},
|
|
2697
|
-
dispose() {
|
|
2698
|
-
transport.dispose();
|
|
2699
|
-
bus.clear();
|
|
2551
|
+
for (const [tableId, table] of state.tables.entries()) {
|
|
2552
|
+
const parentInternalId = tableIdToInternalId.get(tableId);
|
|
2553
|
+
if (parentInternalId === void 0) continue;
|
|
2554
|
+
for (const [colId, col] of Object.entries(table.columns)) {
|
|
2555
|
+
parentIds.push(parentInternalId);
|
|
2556
|
+
colIds.push(colId);
|
|
2557
|
+
const ref = typeof col.colRef === "number" ? col.colRef : columnIds.length + 1;
|
|
2558
|
+
columnIds.push(ref);
|
|
2559
|
+
types.push(col.type ?? null);
|
|
2560
|
+
labels.push(col.label ?? null);
|
|
2561
|
+
parentPos.push(col.parentPos ?? null);
|
|
2562
|
+
isFormula.push(Boolean(col.isFormula));
|
|
2563
|
+
const formula = typeof col.formula === "string" ? col.formula : null;
|
|
2564
|
+
formulas.push(formula);
|
|
2565
|
+
descriptions.push(col.description ?? null);
|
|
2566
|
+
widgetOptions.push(col.widgetOptions ?? null);
|
|
2567
|
+
displayCol.push(typeof col.displayColRef === "number" ? col.displayColRef : null);
|
|
2568
|
+
visibleCol.push(typeof col.visibleColRef === "number" ? col.visibleColRef : null);
|
|
2700
2569
|
}
|
|
2570
|
+
}
|
|
2571
|
+
return {
|
|
2572
|
+
parentId: parentIds,
|
|
2573
|
+
colId: colIds,
|
|
2574
|
+
id: columnIds,
|
|
2575
|
+
type: types,
|
|
2576
|
+
label: labels,
|
|
2577
|
+
parentPos,
|
|
2578
|
+
isFormula,
|
|
2579
|
+
formula: formulas,
|
|
2580
|
+
description: descriptions,
|
|
2581
|
+
// Also expose legacy `descr` for robustness.
|
|
2582
|
+
descr: descriptions,
|
|
2583
|
+
widgetOptions,
|
|
2584
|
+
displayCol,
|
|
2585
|
+
visibleCol
|
|
2701
2586
|
};
|
|
2702
2587
|
}
|
|
2703
2588
|
|
|
2704
|
-
// src/emulator/
|
|
2705
|
-
var
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
}
|
|
2589
|
+
// src/emulator/core/action-engine.ts
|
|
2590
|
+
var EmulatorActionError = class extends Error {
|
|
2591
|
+
constructor(action, message2) {
|
|
2592
|
+
super(message2);
|
|
2593
|
+
this.action = action;
|
|
2594
|
+
this.name = "EmulatorActionError";
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
function applyUserActions(state, bus, actions) {
|
|
2598
|
+
const retValues = [];
|
|
2599
|
+
const changed = /* @__PURE__ */ new Set();
|
|
2600
|
+
for (const action of actions) {
|
|
2601
|
+
bus.emit({ type: "user-action", action });
|
|
2602
|
+
const ret = applySingle(state, action, changed);
|
|
2603
|
+
retValues.push(ret);
|
|
2604
|
+
}
|
|
2605
|
+
for (const tableId of changed) {
|
|
2606
|
+
bus.emit({ type: "data-change", tableId });
|
|
2607
|
+
}
|
|
2608
|
+
return { retValues, changedTableIds: [...changed] };
|
|
2711
2609
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
label: "Tasks",
|
|
2746
|
-
columns: {
|
|
2747
|
-
title: { type: "Text", label: "Title" },
|
|
2748
|
-
done: { type: "Bool", label: "Done" },
|
|
2749
|
-
priority: { type: "Choice", label: "Priority" }
|
|
2750
|
-
},
|
|
2751
|
-
rows: [
|
|
2752
|
-
{ id: 1, title: "Buy milk", done: false, priority: "high" },
|
|
2753
|
-
{ id: 2, title: "Write the SOTA emulator", done: true, priority: "high" },
|
|
2754
|
-
{ id: 3, title: "Ship it", done: false, priority: "medium" }
|
|
2755
|
-
]
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
});
|
|
2759
|
-
},
|
|
2760
|
-
contacts() {
|
|
2761
|
-
return replicaBase({
|
|
2762
|
-
docName: "Contacts",
|
|
2763
|
-
mode: "schema+data",
|
|
2764
|
-
tables: {
|
|
2765
|
-
Contacts: {
|
|
2766
|
-
label: "Contacts",
|
|
2767
|
-
columns: {
|
|
2768
|
-
firstName: { type: "Text" },
|
|
2769
|
-
lastName: { type: "Text" },
|
|
2770
|
-
email: { type: "Text" },
|
|
2771
|
-
company: { type: "Text" }
|
|
2772
|
-
},
|
|
2773
|
-
rows: [
|
|
2774
|
-
{ id: 1, firstName: "Ada", lastName: "Lovelace", email: "ada@analytical.engine", company: "British Crown" },
|
|
2775
|
-
{ id: 2, firstName: "Linus", lastName: "Torvalds", email: "linus@kernel.org", company: "Linux Foundation" },
|
|
2776
|
-
{ id: 3, firstName: "Margaret", lastName: "Hamilton", email: "margaret@apollo.nasa", company: "NASA" }
|
|
2777
|
-
]
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
});
|
|
2610
|
+
function applySingle(state, action, changed) {
|
|
2611
|
+
const [name, ...args] = action;
|
|
2612
|
+
switch (name) {
|
|
2613
|
+
case "AddRecord":
|
|
2614
|
+
return addRecord(state, args, changed);
|
|
2615
|
+
case "BulkAddRecord":
|
|
2616
|
+
return bulkAddRecord(state, args, changed);
|
|
2617
|
+
case "UpdateRecord":
|
|
2618
|
+
return updateRecord(state, args, changed);
|
|
2619
|
+
case "BulkUpdateRecord":
|
|
2620
|
+
return bulkUpdateRecord(state, args, changed);
|
|
2621
|
+
case "RemoveRecord":
|
|
2622
|
+
return removeRecord(state, args, changed);
|
|
2623
|
+
case "BulkRemoveRecord":
|
|
2624
|
+
return bulkRemoveRecord(state, args, changed);
|
|
2625
|
+
case "AddColumn":
|
|
2626
|
+
return addColumn(state, args);
|
|
2627
|
+
case "AddVisibleColumn":
|
|
2628
|
+
return addColumn(state, args);
|
|
2629
|
+
case "RemoveColumn":
|
|
2630
|
+
return removeColumn(state, args, changed);
|
|
2631
|
+
case "RenameColumn":
|
|
2632
|
+
return renameColumn(state, args, changed);
|
|
2633
|
+
case "ModifyColumn":
|
|
2634
|
+
return modifyColumn(state, args, changed);
|
|
2635
|
+
case "AddTable":
|
|
2636
|
+
return addTable(state, args);
|
|
2637
|
+
case "RemoveTable":
|
|
2638
|
+
return removeTable(state, args);
|
|
2639
|
+
case "RenameTable":
|
|
2640
|
+
return renameTable(state, args);
|
|
2641
|
+
default:
|
|
2642
|
+
throw new EmulatorActionError(action, `Unsupported user action: ${String(name)}`);
|
|
2781
2643
|
}
|
|
2782
|
-
};
|
|
2783
|
-
|
|
2784
|
-
// src/emulator/testing/render-with-grist.tsx
|
|
2785
|
-
function renderWithGrist(ui, options = {}) {
|
|
2786
|
-
fakeIframeContext();
|
|
2787
|
-
resetGristForTests();
|
|
2788
|
-
const emulator = options.existing ?? createGristEmulator(options.emulator ?? { document: presets.blank() });
|
|
2789
|
-
const result = (0, import_react2.render)(ui, options);
|
|
2790
|
-
const originalUnmount = result.unmount;
|
|
2791
|
-
result.unmount = () => {
|
|
2792
|
-
originalUnmount();
|
|
2793
|
-
if (!options.existing) emulator.dispose();
|
|
2794
|
-
resetGristForTests();
|
|
2795
|
-
};
|
|
2796
|
-
return Object.assign(result, { emulator });
|
|
2797
2644
|
}
|
|
2798
|
-
function
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2645
|
+
function addRecord(state, [tableId, rowId, values], changed) {
|
|
2646
|
+
const table = getTable(state, requireString(tableId, "AddRecord.tableId"));
|
|
2647
|
+
const id = typeof rowId === "number" ? rowId : nextRowId(table);
|
|
2648
|
+
const normalizedValues = normalizeValuesForTable(
|
|
2649
|
+
table,
|
|
2650
|
+
values ?? {}
|
|
2651
|
+
);
|
|
2652
|
+
const row = { id, ...normalizedValues };
|
|
2653
|
+
ensureColumns(table, normalizedValues);
|
|
2654
|
+
evaluateRowFormulas(state, table, row);
|
|
2655
|
+
syncRefListDisplayHelpers(state, table, row);
|
|
2656
|
+
table.rows.push(row);
|
|
2657
|
+
changed.add(table.tableId);
|
|
2658
|
+
return id;
|
|
2808
2659
|
}
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2660
|
+
function bulkAddRecord(state, [tableId, rowIds, columnar], changed) {
|
|
2661
|
+
const table = getTable(state, requireString(tableId, "BulkAddRecord.tableId"));
|
|
2662
|
+
const cols = normalizeColumnarForTable(
|
|
2663
|
+
table,
|
|
2664
|
+
columnar ?? {}
|
|
2665
|
+
);
|
|
2666
|
+
const ids = Array.isArray(rowIds) ? rowIds : [];
|
|
2667
|
+
const length = ids.length || lengthOf(cols);
|
|
2668
|
+
const out = [];
|
|
2669
|
+
ensureColumns(table, cols, true);
|
|
2670
|
+
for (let i = 0; i < length; i++) {
|
|
2671
|
+
const id = typeof ids[i] === "number" ? ids[i] : nextRowId(table);
|
|
2672
|
+
const row = { id };
|
|
2673
|
+
for (const colId of Object.keys(cols)) row[colId] = cols[colId][i];
|
|
2674
|
+
evaluateRowFormulas(state, table, row);
|
|
2675
|
+
syncRefListDisplayHelpers(state, table, row);
|
|
2676
|
+
table.rows.push(row);
|
|
2677
|
+
out.push(id);
|
|
2678
|
+
}
|
|
2679
|
+
changed.add(table.tableId);
|
|
2680
|
+
return out;
|
|
2824
2681
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
};
|
|
2841
|
-
var cardStyle = {
|
|
2842
|
-
background: "var(--card)",
|
|
2843
|
-
border: "1px solid var(--border)",
|
|
2844
|
-
borderRadius: 12,
|
|
2845
|
-
boxShadow: "0 1px 3px rgb(0 0 0 / 0.12)",
|
|
2846
|
-
maxWidth: 420,
|
|
2847
|
-
padding: "20px 22px",
|
|
2848
|
-
textAlign: "center",
|
|
2849
|
-
width: "100%"
|
|
2850
|
-
};
|
|
2851
|
-
var titleStyle = {
|
|
2852
|
-
color: "var(--foreground)",
|
|
2853
|
-
fontSize: 15,
|
|
2854
|
-
fontWeight: 600,
|
|
2855
|
-
lineHeight: 1.35,
|
|
2856
|
-
margin: 0
|
|
2857
|
-
};
|
|
2858
|
-
var messageStyle = {
|
|
2859
|
-
color: "var(--muted-foreground)",
|
|
2860
|
-
fontSize: 14,
|
|
2861
|
-
lineHeight: 1.5,
|
|
2862
|
-
margin: "10px 0 0"
|
|
2863
|
-
};
|
|
2864
|
-
var buttonStyle = {
|
|
2865
|
-
background: "var(--background)",
|
|
2866
|
-
border: "1px solid var(--border)",
|
|
2867
|
-
borderRadius: 8,
|
|
2868
|
-
color: "var(--foreground)",
|
|
2869
|
-
cursor: "pointer",
|
|
2870
|
-
fontSize: 13,
|
|
2871
|
-
fontWeight: 500,
|
|
2872
|
-
marginTop: 16,
|
|
2873
|
-
padding: "8px 14px"
|
|
2874
|
-
};
|
|
2875
|
-
function GristBoundaryScreen({
|
|
2876
|
-
title,
|
|
2877
|
-
message: message2,
|
|
2878
|
-
tone = "default",
|
|
2879
|
-
showRetry = false,
|
|
2880
|
-
actions,
|
|
2881
|
-
retryLabel = "Try reconnecting",
|
|
2882
|
-
retryBusyLabel = "Reconnecting\u2026"
|
|
2883
|
-
}) {
|
|
2884
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: shellStyle, role: tone === "error" ? "alert" : "status", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardStyle, children: [
|
|
2885
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: titleStyle, children: title }),
|
|
2886
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: messageStyle, children: message2 }),
|
|
2887
|
-
showRetry && actions ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2888
|
-
"button",
|
|
2889
|
-
{
|
|
2890
|
-
disabled: actions.isReloading,
|
|
2891
|
-
onClick: () => void actions.reload(),
|
|
2892
|
-
style: {
|
|
2893
|
-
...buttonStyle,
|
|
2894
|
-
cursor: actions.isReloading ? "not-allowed" : "pointer",
|
|
2895
|
-
opacity: actions.isReloading ? 0.7 : 1
|
|
2896
|
-
},
|
|
2897
|
-
type: "button",
|
|
2898
|
-
children: actions.isReloading ? retryBusyLabel : retryLabel
|
|
2899
|
-
}
|
|
2900
|
-
) : null
|
|
2901
|
-
] }) });
|
|
2682
|
+
function updateRecord(state, [tableId, rowId, values], changed) {
|
|
2683
|
+
const table = getTable(state, requireString(tableId, "UpdateRecord.tableId"));
|
|
2684
|
+
const id = requireNumber(rowId, "UpdateRecord.rowId");
|
|
2685
|
+
const row = findRow(table, id);
|
|
2686
|
+
if (!row) throw new EmulatorActionError(["UpdateRecord", tableId, rowId, values], `Row ${id} not found in ${table.tableId}`);
|
|
2687
|
+
const normalizedValues = normalizeValuesForTable(
|
|
2688
|
+
table,
|
|
2689
|
+
values ?? {}
|
|
2690
|
+
);
|
|
2691
|
+
ensureColumns(table, normalizedValues);
|
|
2692
|
+
Object.assign(row, normalizedValues);
|
|
2693
|
+
evaluateRowFormulas(state, table, row);
|
|
2694
|
+
syncRefListDisplayHelpers(state, table, row);
|
|
2695
|
+
changed.add(table.tableId);
|
|
2696
|
+
return null;
|
|
2902
2697
|
}
|
|
2903
|
-
function
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2698
|
+
function bulkUpdateRecord(state, [tableId, rowIds, columnar], changed) {
|
|
2699
|
+
const table = getTable(state, requireString(tableId, "BulkUpdateRecord.tableId"));
|
|
2700
|
+
const ids = rowIds;
|
|
2701
|
+
const cols = normalizeColumnarForTable(
|
|
2702
|
+
table,
|
|
2703
|
+
columnar ?? {}
|
|
2704
|
+
);
|
|
2705
|
+
ensureColumns(table, cols, true);
|
|
2706
|
+
ids.forEach((id, idx) => {
|
|
2707
|
+
const row = findRow(table, id);
|
|
2708
|
+
if (!row) throw new EmulatorActionError(["BulkUpdateRecord", tableId, ids, cols], `Row ${id} not found in ${table.tableId}`);
|
|
2709
|
+
for (const colId of Object.keys(cols)) row[colId] = cols[colId][idx];
|
|
2710
|
+
evaluateRowFormulas(state, table, row);
|
|
2711
|
+
syncRefListDisplayHelpers(state, table, row);
|
|
2712
|
+
});
|
|
2713
|
+
changed.add(table.tableId);
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
function removeRecord(state, [tableId, rowId], changed) {
|
|
2717
|
+
const table = getTable(state, requireString(tableId, "RemoveRecord.tableId"));
|
|
2718
|
+
const id = requireNumber(rowId, "RemoveRecord.rowId");
|
|
2719
|
+
const idx = findRowIndex(table, id);
|
|
2720
|
+
if (idx >= 0) table.rows.splice(idx, 1);
|
|
2721
|
+
if (state.selection.tableId === table.tableId && state.selection.rowId === id) {
|
|
2722
|
+
state.selection.rowId = table.rows[0]?.id ?? null;
|
|
2926
2723
|
}
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2724
|
+
changed.add(table.tableId);
|
|
2725
|
+
return null;
|
|
2726
|
+
}
|
|
2727
|
+
function bulkRemoveRecord(state, [tableId, rowIds], changed) {
|
|
2728
|
+
const table = getTable(state, requireString(tableId, "BulkRemoveRecord.tableId"));
|
|
2729
|
+
const ids = new Set(rowIds);
|
|
2730
|
+
table.rows = table.rows.filter((r) => !ids.has(r.id));
|
|
2731
|
+
if (state.selection.tableId === table.tableId && typeof state.selection.rowId === "number" && ids.has(state.selection.rowId)) {
|
|
2732
|
+
state.selection.rowId = table.rows[0]?.id ?? null;
|
|
2934
2733
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2734
|
+
changed.add(table.tableId);
|
|
2735
|
+
return null;
|
|
2736
|
+
}
|
|
2737
|
+
function addColumn(state, [tableId, colId, info]) {
|
|
2738
|
+
const table = getTable(state, requireString(tableId, "AddColumn.tableId"));
|
|
2739
|
+
const col = requireString(colId, "AddColumn.colId");
|
|
2740
|
+
if (table.columns[col]) throw new EmulatorActionError(["AddColumn", tableId, colId], `Column ${col} already exists`);
|
|
2741
|
+
table.columns[col] = info ?? {};
|
|
2742
|
+
for (const row of table.rows) {
|
|
2743
|
+
if (!(col in row)) row[col] = null;
|
|
2942
2744
|
}
|
|
2943
2745
|
return null;
|
|
2944
2746
|
}
|
|
2945
|
-
function
|
|
2946
|
-
const
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2747
|
+
function removeColumn(state, [tableId, colId], changed) {
|
|
2748
|
+
const table = getTable(state, requireString(tableId, "RemoveColumn.tableId"));
|
|
2749
|
+
const col = requireString(colId, "RemoveColumn.colId");
|
|
2750
|
+
delete table.columns[col];
|
|
2751
|
+
for (const row of table.rows) delete row[col];
|
|
2752
|
+
changed.add(table.tableId);
|
|
2753
|
+
return null;
|
|
2754
|
+
}
|
|
2755
|
+
function renameColumn(state, [tableId, colId, newColId], changed) {
|
|
2756
|
+
const table = getTable(state, requireString(tableId, "RenameColumn.tableId"));
|
|
2757
|
+
const from = requireString(colId, "RenameColumn.colId");
|
|
2758
|
+
const to = requireString(newColId, "RenameColumn.newColId");
|
|
2759
|
+
if (!table.columns[from]) throw new EmulatorActionError(["RenameColumn", tableId, from, to], `Column ${from} not found`);
|
|
2760
|
+
if (from === to) return null;
|
|
2761
|
+
table.columns[to] = table.columns[from];
|
|
2762
|
+
delete table.columns[from];
|
|
2763
|
+
for (const row of table.rows) {
|
|
2764
|
+
if (from in row) {
|
|
2765
|
+
row[to] = row[from];
|
|
2766
|
+
delete row[from];
|
|
2958
2767
|
}
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2768
|
+
}
|
|
2769
|
+
changed.add(table.tableId);
|
|
2770
|
+
return null;
|
|
2771
|
+
}
|
|
2772
|
+
function modifyColumn(state, [tableId, colId, info], changed) {
|
|
2773
|
+
const table = getTable(state, requireString(tableId, "ModifyColumn.tableId"));
|
|
2774
|
+
const col = requireString(colId, "ModifyColumn.colId");
|
|
2775
|
+
if (!table.columns[col]) throw new EmulatorActionError(["ModifyColumn", tableId, colId], `Column ${col} not found`);
|
|
2776
|
+
table.columns[col] = { ...table.columns[col], ...info };
|
|
2777
|
+
changed.add(table.tableId);
|
|
2778
|
+
return null;
|
|
2779
|
+
}
|
|
2780
|
+
function addTable(state, [tableId, columns]) {
|
|
2781
|
+
const id = requireString(tableId, "AddTable.tableId");
|
|
2782
|
+
if (state.tables.has(id)) throw new EmulatorActionError(["AddTable", tableId], `Table ${id} already exists`);
|
|
2783
|
+
const cols = {};
|
|
2784
|
+
for (const c of columns ?? []) {
|
|
2785
|
+
cols[c.id] = { ...c };
|
|
2786
|
+
}
|
|
2787
|
+
state.tables.set(id, { tableId: id, columns: cols, rows: [] });
|
|
2788
|
+
return id;
|
|
2789
|
+
}
|
|
2790
|
+
function removeTable(state, [tableId]) {
|
|
2791
|
+
const id = requireString(tableId, "RemoveTable.tableId");
|
|
2792
|
+
state.tables.delete(id);
|
|
2793
|
+
if (state.selection.tableId === id) {
|
|
2794
|
+
const fallback = state.tables.keys().next().value;
|
|
2795
|
+
if (fallback) {
|
|
2796
|
+
state.selection.tableId = fallback;
|
|
2797
|
+
state.selection.rowId = null;
|
|
2972
2798
|
}
|
|
2973
|
-
|
|
2799
|
+
}
|
|
2800
|
+
return null;
|
|
2974
2801
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
primaryForeground: token("primary-foreground", "#fafafa"),
|
|
2986
|
-
destructive: token("destructive", "#dc2626"),
|
|
2987
|
-
radius: token("radius", "0.625rem")
|
|
2988
|
-
};
|
|
2989
|
-
var pageStyle = {
|
|
2990
|
-
boxSizing: "border-box",
|
|
2991
|
-
display: "flex",
|
|
2992
|
-
flexDirection: "column",
|
|
2993
|
-
justifyContent: "center",
|
|
2994
|
-
maxWidth: "28rem",
|
|
2995
|
-
minHeight: "100%",
|
|
2996
|
-
margin: "0 auto",
|
|
2997
|
-
padding: "4rem 1.5rem",
|
|
2998
|
-
color: tokens.foreground,
|
|
2999
|
-
font: "inherit"
|
|
3000
|
-
};
|
|
3001
|
-
var eyebrowStyle = {
|
|
3002
|
-
margin: "0 0 0.5rem",
|
|
3003
|
-
textAlign: "center",
|
|
3004
|
-
fontSize: "0.875rem",
|
|
3005
|
-
fontWeight: 500,
|
|
3006
|
-
color: tokens.mutedForeground
|
|
3007
|
-
};
|
|
3008
|
-
var headingStyle = {
|
|
3009
|
-
margin: 0,
|
|
3010
|
-
textAlign: "center",
|
|
3011
|
-
fontSize: "1.5rem",
|
|
3012
|
-
fontWeight: 600,
|
|
3013
|
-
letterSpacing: "-0.01em"
|
|
3014
|
-
};
|
|
3015
|
-
var leadStyle = {
|
|
3016
|
-
margin: "0.75rem auto 2rem",
|
|
3017
|
-
maxWidth: "24rem",
|
|
3018
|
-
textAlign: "center",
|
|
3019
|
-
fontSize: "0.875rem",
|
|
3020
|
-
lineHeight: 1.5,
|
|
3021
|
-
color: tokens.mutedForeground
|
|
3022
|
-
};
|
|
3023
|
-
var cardStyle2 = {
|
|
3024
|
-
boxSizing: "border-box",
|
|
3025
|
-
marginBottom: "1rem",
|
|
3026
|
-
padding: "1.25rem",
|
|
3027
|
-
border: `1px solid ${tokens.border}`,
|
|
3028
|
-
borderRadius: tokens.radius,
|
|
3029
|
-
background: tokens.card
|
|
3030
|
-
};
|
|
3031
|
-
var cardTitleStyle = {
|
|
3032
|
-
margin: "0 0 0.375rem",
|
|
3033
|
-
fontSize: "1rem",
|
|
3034
|
-
fontWeight: 600
|
|
3035
|
-
};
|
|
3036
|
-
var cardTextStyle = {
|
|
3037
|
-
margin: 0,
|
|
3038
|
-
fontSize: "0.875rem",
|
|
3039
|
-
lineHeight: 1.5,
|
|
3040
|
-
color: tokens.mutedForeground
|
|
3041
|
-
};
|
|
3042
|
-
var codeStyle = {
|
|
3043
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
3044
|
-
fontSize: "0.8125rem",
|
|
3045
|
-
color: tokens.foreground
|
|
3046
|
-
};
|
|
3047
|
-
|
|
3048
|
-
// src/app/chrome/widget-title.ts
|
|
3049
|
-
function widgetTitle() {
|
|
3050
|
-
const title = typeof document === "undefined" ? "" : document.title.trim();
|
|
3051
|
-
return title || "Grist widget";
|
|
2802
|
+
function renameTable(state, [from, to]) {
|
|
2803
|
+
const fromId = requireString(from, "RenameTable.from");
|
|
2804
|
+
const toId = requireString(to, "RenameTable.to");
|
|
2805
|
+
const table = state.tables.get(fromId);
|
|
2806
|
+
if (!table) throw new EmulatorActionError(["RenameTable", fromId], `Table ${fromId} not found`);
|
|
2807
|
+
state.tables.delete(fromId);
|
|
2808
|
+
table.tableId = toId;
|
|
2809
|
+
state.tables.set(toId, table);
|
|
2810
|
+
if (state.selection.tableId === fromId) state.selection.tableId = toId;
|
|
2811
|
+
return null;
|
|
3052
2812
|
}
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
" \xB7 local dev"
|
|
3061
|
-
] }),
|
|
3062
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { style: headingStyle, children: "Not embedded in Grist" }),
|
|
3063
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: leadStyle, children: "This is the local dev server: there's no Grist here to render the widget against." }),
|
|
3064
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
|
|
3065
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Check it against real Grist" }),
|
|
3066
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
|
|
3067
|
-
"Push to ",
|
|
3068
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "dev" }),
|
|
3069
|
-
", then open its ",
|
|
3070
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "/dev/" }),
|
|
3071
|
-
" URL in a custom widget of a real Grist document: later pushes reload it there within seconds. Locally,",
|
|
3072
|
-
" ",
|
|
3073
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "pnpm test" }),
|
|
3074
|
-
" runs the widget against an emulated Grist."
|
|
3075
|
-
] })
|
|
3076
|
-
] }),
|
|
3077
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: cardStyle2, children: [
|
|
3078
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: cardTitleStyle, children: "Starting from another example?" }),
|
|
3079
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: cardTextStyle, children: [
|
|
3080
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "npx create-grist-widget list" }),
|
|
3081
|
-
" shows the examples, and",
|
|
3082
|
-
" ",
|
|
3083
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { style: codeStyle, children: "npx create-grist-widget use <example>" }),
|
|
3084
|
-
" swaps this widget for one."
|
|
3085
|
-
] })
|
|
3086
|
-
] })
|
|
3087
|
-
] });
|
|
2813
|
+
function ensureColumns(table, values, bulk = false) {
|
|
2814
|
+
for (const col of Object.keys(values)) {
|
|
2815
|
+
if (!table.columns[col]) {
|
|
2816
|
+
const sample = bulk ? values[col]?.[0] : values[col];
|
|
2817
|
+
table.columns[col] = inferType(sample);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
3088
2820
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
if (last === "latest") channel = { kind: "latest" };
|
|
3100
|
-
else if (last === "dev") channel = { kind: "dev" };
|
|
3101
|
-
else {
|
|
3102
|
-
const match = VERSION_SEGMENT.exec(last);
|
|
3103
|
-
if (match) channel = { kind: "version", version: match[1] };
|
|
2821
|
+
function inferType(sample) {
|
|
2822
|
+
switch (typeof sample) {
|
|
2823
|
+
case "number":
|
|
2824
|
+
return { type: "Numeric" };
|
|
2825
|
+
case "boolean":
|
|
2826
|
+
return { type: "Bool" };
|
|
2827
|
+
case "string":
|
|
2828
|
+
return { type: "Text" };
|
|
2829
|
+
default:
|
|
2830
|
+
return { type: "Any" };
|
|
3104
2831
|
}
|
|
3105
|
-
if (!channel) return { channel: { kind: "latest" }, basePath: withTrailingSlash(pathname) };
|
|
3106
|
-
return { channel, basePath: withTrailingSlash(segments.slice(0, -1).join("/")) };
|
|
3107
2832
|
}
|
|
3108
|
-
function
|
|
3109
|
-
|
|
2833
|
+
function lengthOf(cols) {
|
|
2834
|
+
for (const arr of Object.values(cols)) if (Array.isArray(arr)) return arr.length;
|
|
2835
|
+
return 0;
|
|
3110
2836
|
}
|
|
3111
|
-
function
|
|
3112
|
-
|
|
2837
|
+
function requireString(value, name) {
|
|
2838
|
+
if (typeof value !== "string" || !value) throw new Error(`Expected non-empty string for ${name}`);
|
|
2839
|
+
return value;
|
|
3113
2840
|
}
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
function useReleasedVersions(pathname = typeof window === "undefined" ? "/" : window.location.pathname) {
|
|
3118
|
-
const url = `${parseDeployPath(pathname).basePath}versions.json`;
|
|
3119
|
-
const [result, setResult] = (0, import_react5.useState)({ versions: null, error: false });
|
|
3120
|
-
(0, import_react5.useEffect)(() => {
|
|
3121
|
-
let cancelled = false;
|
|
3122
|
-
fetch(url, { cache: "no-store" }).then((res) => res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`))).then((data) => {
|
|
3123
|
-
if (cancelled) return;
|
|
3124
|
-
if (!Array.isArray(data)) throw new Error("versions.json is not an array");
|
|
3125
|
-
setResult({ versions: data, error: false });
|
|
3126
|
-
}).catch(() => {
|
|
3127
|
-
if (!cancelled) setResult({ versions: null, error: true });
|
|
3128
|
-
});
|
|
3129
|
-
return () => {
|
|
3130
|
-
cancelled = true;
|
|
3131
|
-
};
|
|
3132
|
-
}, [url]);
|
|
3133
|
-
return result;
|
|
2841
|
+
function requireNumber(value, name) {
|
|
2842
|
+
if (typeof value !== "number" || Number.isNaN(value)) throw new Error(`Expected number for ${name}`);
|
|
2843
|
+
return value;
|
|
3134
2844
|
}
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
return
|
|
3141
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: eyebrowStyle, children: [
|
|
3142
|
-
widgetTitle(),
|
|
3143
|
-
" \xB7 ",
|
|
3144
|
-
label
|
|
3145
|
-
] }),
|
|
3146
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { style: headingStyle, children: "Grist isn't loaded here" }),
|
|
3147
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: leadStyle, children: [
|
|
3148
|
-
"This is the ",
|
|
3149
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: codeStyle, children: label }),
|
|
3150
|
-
" build, opened outside Grist. Hover a version to see when it was released."
|
|
3151
|
-
] }),
|
|
3152
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChannelLinks, { current: channel }),
|
|
3153
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InstallUrl, {})
|
|
3154
|
-
] });
|
|
2845
|
+
function normalizeValuesForTable(table, values) {
|
|
2846
|
+
const out = {};
|
|
2847
|
+
for (const [colId, value] of Object.entries(values)) {
|
|
2848
|
+
out[colId] = normalizeCellValueForType(value, table.columns[colId]?.type);
|
|
2849
|
+
}
|
|
2850
|
+
return out;
|
|
3155
2851
|
}
|
|
3156
|
-
function
|
|
3157
|
-
const
|
|
3158
|
-
const
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
versions?.map((v) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3164
|
-
ChannelLink,
|
|
3165
|
-
{
|
|
3166
|
-
href: `${basePath}v${v.version}/`,
|
|
3167
|
-
title: formatDate(v.publishedAt),
|
|
3168
|
-
active: isCurrent({ kind: "version", version: v.version }),
|
|
3169
|
-
children: `v${v.version}`
|
|
3170
|
-
},
|
|
3171
|
-
v.version
|
|
3172
|
-
))
|
|
3173
|
-
] });
|
|
2852
|
+
function normalizeColumnarForTable(table, columns) {
|
|
2853
|
+
const out = {};
|
|
2854
|
+
for (const [colId, values] of Object.entries(columns)) {
|
|
2855
|
+
const type = table.columns[colId]?.type;
|
|
2856
|
+
out[colId] = Array.isArray(values) ? values.map((value) => normalizeCellValueForType(value, type)) : [];
|
|
2857
|
+
}
|
|
2858
|
+
return out;
|
|
3174
2859
|
}
|
|
3175
|
-
function
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
const style = {
|
|
3182
|
-
padding: "0.25rem 0.75rem",
|
|
3183
|
-
border: `1px solid ${active ? tokens.primary : tokens.border}`,
|
|
3184
|
-
borderRadius: "9999px",
|
|
3185
|
-
background: active ? tokens.primary : "transparent",
|
|
3186
|
-
color: active ? tokens.primaryForeground : tokens.mutedForeground,
|
|
3187
|
-
fontSize: "0.75rem",
|
|
3188
|
-
fontWeight: 500,
|
|
3189
|
-
textDecoration: "none"
|
|
3190
|
-
};
|
|
3191
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href, title, "aria-current": active ? "page" : void 0, style, children });
|
|
2860
|
+
function normalizeCellValueForType(value, type) {
|
|
2861
|
+
if (value === null || value === void 0 || typeof type !== "string") return value;
|
|
2862
|
+
if (isListColumnType(type) && Array.isArray(value) && value[0] !== "L") return ["E", "KeyError"];
|
|
2863
|
+
if (type.startsWith("Ref:")) return normalizeRefValue(value);
|
|
2864
|
+
if (type.startsWith("RefList:")) return normalizeRefListValue(value);
|
|
2865
|
+
return value;
|
|
3192
2866
|
}
|
|
3193
|
-
function
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
setCopied(true);
|
|
3199
|
-
setTimeout(() => setCopied(false), 1500);
|
|
3200
|
-
});
|
|
2867
|
+
function extractRefRowId(value) {
|
|
2868
|
+
if (value === null || value === void 0) return null;
|
|
2869
|
+
if (value && typeof value === "object" && "__ref" in value) {
|
|
2870
|
+
const inner = value.rowId;
|
|
2871
|
+
return extractRefRowId(inner);
|
|
3201
2872
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
{
|
|
3209
|
-
readOnly: true,
|
|
3210
|
-
value: url,
|
|
3211
|
-
"aria-label": "Widget URL",
|
|
3212
|
-
onFocus: (event) => event.target.select(),
|
|
3213
|
-
style: {
|
|
3214
|
-
flex: 1,
|
|
3215
|
-
minWidth: 0,
|
|
3216
|
-
padding: "0.5rem 0.75rem",
|
|
3217
|
-
border: `1px solid ${tokens.border}`,
|
|
3218
|
-
borderRadius: tokens.radius,
|
|
3219
|
-
background: "transparent",
|
|
3220
|
-
color: tokens.foreground,
|
|
3221
|
-
font: "inherit",
|
|
3222
|
-
fontSize: "0.875rem"
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3225
|
-
),
|
|
3226
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3227
|
-
"button",
|
|
3228
|
-
{
|
|
3229
|
-
type: "button",
|
|
3230
|
-
onClick: copy,
|
|
3231
|
-
style: {
|
|
3232
|
-
padding: "0.5rem 1rem",
|
|
3233
|
-
border: "none",
|
|
3234
|
-
borderRadius: tokens.radius,
|
|
3235
|
-
background: tokens.primary,
|
|
3236
|
-
color: tokens.primaryForeground,
|
|
3237
|
-
font: "inherit",
|
|
3238
|
-
fontSize: "0.875rem",
|
|
3239
|
-
fontWeight: 500,
|
|
3240
|
-
cursor: "pointer"
|
|
3241
|
-
},
|
|
3242
|
-
children: copied ? "Copied" : "Copy"
|
|
3243
|
-
}
|
|
3244
|
-
)
|
|
3245
|
-
] })
|
|
3246
|
-
] });
|
|
2873
|
+
if (value && typeof value === "object" && "rowId" in value) {
|
|
2874
|
+
return extractRefRowId(value.rowId);
|
|
2875
|
+
}
|
|
2876
|
+
const fromTuple = coerceGristDocApiRefRowId(value);
|
|
2877
|
+
if (fromTuple !== void 0 && fromTuple > 0) return Math.trunc(fromTuple);
|
|
2878
|
+
return toPositiveInt(value);
|
|
3247
2879
|
}
|
|
3248
|
-
function
|
|
3249
|
-
|
|
3250
|
-
return Number.isNaN(date.getTime()) ? iso : date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
2880
|
+
function isListColumnType(type) {
|
|
2881
|
+
return type === "ChoiceList" || type === "Attachments" || type.startsWith("RefList:");
|
|
3251
2882
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
var TITLE = {
|
|
3255
|
-
"column-mapping": "Column mapping",
|
|
3256
|
-
"mapping-pending": "Column mapping",
|
|
3257
|
-
"section-not-linked": "Widget linking",
|
|
3258
|
-
"source-not-wired": "Widget linking",
|
|
3259
|
-
"action-error": "Action error",
|
|
3260
|
-
"access-insufficient": "Access level",
|
|
3261
|
-
"map-back-skip": "Write skipped",
|
|
3262
|
-
"cell-issues": "Unexpected values"
|
|
3263
|
-
};
|
|
3264
|
-
var SEVERITY = {
|
|
3265
|
-
"column-mapping": "warning",
|
|
3266
|
-
"mapping-pending": "info",
|
|
3267
|
-
"section-not-linked": "warning",
|
|
3268
|
-
"source-not-wired": "warning",
|
|
3269
|
-
"action-error": "error",
|
|
3270
|
-
"access-insufficient": "error",
|
|
3271
|
-
"map-back-skip": "warning",
|
|
3272
|
-
"cell-issues": "warning"
|
|
3273
|
-
};
|
|
3274
|
-
var alert = (kind, message2, ariaRole = "status") => ({
|
|
3275
|
-
id: kind,
|
|
3276
|
-
kind,
|
|
3277
|
-
severity: SEVERITY[kind],
|
|
3278
|
-
title: TITLE[kind],
|
|
3279
|
-
ariaRole,
|
|
3280
|
-
message: message2
|
|
3281
|
-
});
|
|
3282
|
-
function isWidgetSectionNotLinked(interaction) {
|
|
3283
|
-
const linking = parseGristWidgetLinkingInfo(interaction?.linking);
|
|
3284
|
-
return Boolean(linking) && linking.asTarget == null;
|
|
2883
|
+
function normalizeRefValue(value) {
|
|
2884
|
+
return extractRefRowId(value);
|
|
3285
2885
|
}
|
|
3286
|
-
function
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
if (
|
|
3291
|
-
|
|
3292
|
-
const parts = ["Column mapping is incomplete."];
|
|
3293
|
-
if (mapping.missing.length) parts.push(`Required columns: ${mapping.missing.join(", ")}.`);
|
|
3294
|
-
if (mapping.emptyMultiples.length) parts.push(`Assign at least one sheet column for: ${mapping.emptyMultiples.join(", ")}.`);
|
|
3295
|
-
out.push(alert("column-mapping", parts.join(" ")));
|
|
2886
|
+
function normalizeRefListValue(value) {
|
|
2887
|
+
const toIdList = (items) => Array.from(
|
|
2888
|
+
new Set(items.map((item) => extractRefRowId(item)).filter((item) => item !== null))
|
|
2889
|
+
);
|
|
2890
|
+
if (Array.isArray(value) && value[0] === "L") {
|
|
2891
|
+
return ["L", ...toIdList(value.slice(1))];
|
|
3296
2892
|
}
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
out.push(
|
|
3300
|
-
alert(
|
|
3301
|
-
"access-insufficient",
|
|
3302
|
-
'This operation requires "Full document access". Set the Access level to "Full document access" in the Custom widget configuration panel.',
|
|
3303
|
-
"alert"
|
|
3304
|
-
)
|
|
3305
|
-
);
|
|
3306
|
-
} else if (state.actionError) {
|
|
3307
|
-
out.push(alert("action-error", state.actionError, "alert"));
|
|
2893
|
+
if (Array.isArray(value)) {
|
|
2894
|
+
return ["L", ...toIdList(value)];
|
|
3308
2895
|
}
|
|
3309
|
-
if (
|
|
3310
|
-
const
|
|
3311
|
-
|
|
2896
|
+
if (typeof value === "string") {
|
|
2897
|
+
const tokens2 = value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
2898
|
+
return ["L", ...toIdList(tokens2)];
|
|
3312
2899
|
}
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
const first = cellIssues[0];
|
|
3316
|
-
const where = first.column ? `${first.column} in row ${first.rowId}` : `row ${first.rowId}`;
|
|
3317
|
-
const more = cellIssues.length > 1 ? ` (and ${cellIssues.length - 1} more)` : "";
|
|
3318
|
-
out.push(alert("cell-issues", `${where}: ${first.message}${more}. Check the column's type, or the cell, in Grist.`));
|
|
2900
|
+
if (value && typeof value === "object" && Array.isArray(value.rowIds)) {
|
|
2901
|
+
return ["L", ...toIdList(value.rowIds)];
|
|
3319
2902
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
2903
|
+
return ["L"];
|
|
2904
|
+
}
|
|
2905
|
+
function toPositiveInt(value) {
|
|
2906
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
2907
|
+
if (typeof value === "string") {
|
|
2908
|
+
const n = Number.parseInt(value.trim(), 10);
|
|
2909
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
2910
|
+
}
|
|
2911
|
+
return null;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
// src/emulator/core/columnar.ts
|
|
2915
|
+
function rowsToColumnar(table, rows, options = {}) {
|
|
2916
|
+
const cols = Object.keys(table.columns);
|
|
2917
|
+
const out = { id: rows.map((r) => r.id) };
|
|
2918
|
+
for (const col of cols) {
|
|
2919
|
+
out[col] = rows.map((r) => encodeForWire(r[col], table.columns[col]?.type, options.keepEncoded));
|
|
2920
|
+
}
|
|
2921
|
+
return out;
|
|
2922
|
+
}
|
|
2923
|
+
function rowToRecord(table, row, options = {}) {
|
|
2924
|
+
const out = { id: row.id };
|
|
2925
|
+
for (const col of Object.keys(table.columns)) {
|
|
2926
|
+
out[col] = encodeForWire(row[col], table.columns[col]?.type, options.keepEncoded);
|
|
2927
|
+
}
|
|
2928
|
+
return out;
|
|
2929
|
+
}
|
|
2930
|
+
function encodeForWire(value, type, keepEncoded) {
|
|
2931
|
+
if (value instanceof Date) {
|
|
2932
|
+
const epoch = Math.floor(value.getTime() / 1e3);
|
|
2933
|
+
if (keepEncoded) {
|
|
2934
|
+
return type && type.startsWith("DateTime") ? ["D", epoch, "UTC"] : ["d", epoch];
|
|
2935
|
+
}
|
|
2936
|
+
return epoch;
|
|
2937
|
+
}
|
|
2938
|
+
return value;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/emulator/api/grist-doc-api.ts
|
|
2942
|
+
function createGristDocApiImpl(state, bus) {
|
|
2943
|
+
return {
|
|
2944
|
+
async getDocName() {
|
|
2945
|
+
return state.docName;
|
|
2946
|
+
},
|
|
2947
|
+
async listTables() {
|
|
2948
|
+
return [...state.tables.keys()];
|
|
2949
|
+
},
|
|
2950
|
+
async fetchTable(tableId) {
|
|
2951
|
+
if (tableId === "_grist_Tables") {
|
|
2952
|
+
return synthesizeTablesMeta(state);
|
|
2953
|
+
}
|
|
2954
|
+
if (tableId === "_grist_Tables_column") {
|
|
2955
|
+
return synthesizeTablesColumnMeta(state);
|
|
2956
|
+
}
|
|
2957
|
+
const table = getTable(state, tableId);
|
|
2958
|
+
return rowsToColumnar(table, table.rows);
|
|
2959
|
+
},
|
|
2960
|
+
async applyUserActions(actions) {
|
|
2961
|
+
const result = applyUserActions(state, bus, actions);
|
|
2962
|
+
return { retValues: result.retValues };
|
|
2963
|
+
},
|
|
2964
|
+
async getAccessToken() {
|
|
2965
|
+
if (!state.accessToken) {
|
|
2966
|
+
const err = new Error("getAccessToken not available (no token configured on emulator)");
|
|
2967
|
+
throw err;
|
|
2968
|
+
}
|
|
2969
|
+
return { ...state.accessToken };
|
|
2970
|
+
}
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/emulator/api/grist-view.ts
|
|
2975
|
+
function createGristViewImpl(state, bus) {
|
|
2976
|
+
return {
|
|
2977
|
+
async fetchSelectedTable(options) {
|
|
2978
|
+
const table = getSelectedTable(state);
|
|
2979
|
+
return rowsToColumnar(table, table.rows, { keepEncoded: options?.keepEncoded });
|
|
2980
|
+
},
|
|
2981
|
+
async fetchSelectedRecord(rowId, options) {
|
|
2982
|
+
const table = getSelectedTable(state);
|
|
2983
|
+
const row = findRow(table, rowId);
|
|
2984
|
+
if (!row) throw new Error(`No row ${rowId} in ${table.tableId}`);
|
|
2985
|
+
return rowToRecord(table, row, { keepEncoded: options?.keepEncoded });
|
|
2986
|
+
},
|
|
2987
|
+
async allowSelectBy() {
|
|
2988
|
+
return state.configuration?.allowSelectBy ?? false;
|
|
2989
|
+
},
|
|
2990
|
+
async setSelectedRows(rowIds) {
|
|
2991
|
+
if (!rowIds || rowIds.length === 0) {
|
|
2992
|
+
state.selection.rowId = null;
|
|
2993
|
+
} else {
|
|
2994
|
+
state.selection.rowId = rowIds[0];
|
|
2995
|
+
}
|
|
2996
|
+
bus.emit({
|
|
2997
|
+
type: "cursor-change",
|
|
2998
|
+
tableId: state.selection.tableId,
|
|
2999
|
+
rowId: state.selection.rowId
|
|
3000
|
+
});
|
|
3001
|
+
},
|
|
3002
|
+
async setCursorPos(cursor) {
|
|
3003
|
+
const tableId = state.selection.tableId;
|
|
3004
|
+
if (typeof cursor.rowId === "number") state.selection.rowId = cursor.rowId;
|
|
3005
|
+
state.selection.cursor = { ...state.selection.cursor, ...cursor };
|
|
3006
|
+
bus.emit({
|
|
3007
|
+
type: "cursor-change",
|
|
3008
|
+
tableId,
|
|
3009
|
+
rowId: state.selection.rowId,
|
|
3010
|
+
cursor: state.selection.cursor
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// src/emulator/api/widget-api.ts
|
|
3017
|
+
function createWidgetApiImpl(state, bus) {
|
|
3018
|
+
function emit() {
|
|
3019
|
+
bus.emit({
|
|
3020
|
+
type: "options-change",
|
|
3021
|
+
options: { ...state.widgetOptions },
|
|
3022
|
+
interaction: { ...state.widgetInteraction }
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
return {
|
|
3026
|
+
async getOption(key) {
|
|
3027
|
+
return state.widgetOptions[key];
|
|
3028
|
+
},
|
|
3029
|
+
async setOption(key, value) {
|
|
3030
|
+
state.widgetOptions[key] = value;
|
|
3031
|
+
emit();
|
|
3032
|
+
},
|
|
3033
|
+
async getOptions() {
|
|
3034
|
+
return Object.keys(state.widgetOptions).length > 0 ? { ...state.widgetOptions } : null;
|
|
3035
|
+
},
|
|
3036
|
+
// Replaces every option, as Grist's WidgetAPI.setOptions does.
|
|
3037
|
+
async setOptions(options) {
|
|
3038
|
+
state.widgetOptions = { ...options };
|
|
3039
|
+
emit();
|
|
3040
|
+
},
|
|
3041
|
+
async clearOptions() {
|
|
3042
|
+
state.widgetOptions = {};
|
|
3043
|
+
emit();
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// src/emulator/api/index.ts
|
|
3049
|
+
function createApiBundle(state, bus) {
|
|
3050
|
+
return {
|
|
3051
|
+
GristView: createGristViewImpl(state, bus),
|
|
3052
|
+
GristDocAPI: createGristDocApiImpl(state, bus),
|
|
3053
|
+
WidgetAPI: createWidgetApiImpl(state, bus),
|
|
3054
|
+
CustomSectionAPI: createCustomSectionApiImpl(state, bus),
|
|
3055
|
+
GristAPI: createGristApiImpl(),
|
|
3056
|
+
CommandAPI: createCommandApiImpl(bus)
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
// src/emulator/core/event-bus.ts
|
|
3061
|
+
function createEventBus(options = {}) {
|
|
3062
|
+
const limit = options.historyLimit ?? 200;
|
|
3063
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3064
|
+
const history = [];
|
|
3065
|
+
return {
|
|
3066
|
+
emit(event) {
|
|
3067
|
+
history.push(event);
|
|
3068
|
+
if (history.length > limit) history.shift();
|
|
3069
|
+
for (const fn of [...listeners]) {
|
|
3070
|
+
try {
|
|
3071
|
+
fn(event);
|
|
3072
|
+
} catch (err) {
|
|
3073
|
+
console.error("[grist-emulator] event listener threw:", err);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
},
|
|
3077
|
+
on(listener) {
|
|
3078
|
+
listeners.add(listener);
|
|
3079
|
+
return () => {
|
|
3080
|
+
listeners.delete(listener);
|
|
3081
|
+
};
|
|
3082
|
+
},
|
|
3083
|
+
history() {
|
|
3084
|
+
return history;
|
|
3085
|
+
},
|
|
3086
|
+
clear() {
|
|
3087
|
+
history.length = 0;
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/emulator/theme-message.ts
|
|
3093
|
+
function buildGristThemeHostMessage(appearance, options) {
|
|
3094
|
+
return {
|
|
3095
|
+
theme: {
|
|
3096
|
+
appearance,
|
|
3097
|
+
name: appearance,
|
|
3098
|
+
colors: {}
|
|
3099
|
+
},
|
|
3100
|
+
...options?.fromReady ? { fromReady: true } : {}
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
// src/emulator/transports/inline.ts
|
|
3105
|
+
function createInlineTransport(options) {
|
|
3106
|
+
const { state, bus, api } = options;
|
|
3107
|
+
const target = options.target ?? globalThis;
|
|
3108
|
+
installFakeIframeContext(target);
|
|
3109
|
+
const recordCbs = /* @__PURE__ */ new Map();
|
|
3110
|
+
const recordsCbs = /* @__PURE__ */ new Map();
|
|
3111
|
+
const optionsCbs = /* @__PURE__ */ new Set();
|
|
3112
|
+
const newRecordCbs = /* @__PURE__ */ new Set();
|
|
3113
|
+
const generic = /* @__PURE__ */ new Map();
|
|
3114
|
+
let lastRecord = null;
|
|
3115
|
+
let lastRecords = null;
|
|
3116
|
+
let lastOptions = null;
|
|
3117
|
+
let lastNewRecord = void 0;
|
|
3118
|
+
const buildInteraction = options.buildInteraction ?? (() => ({
|
|
3119
|
+
access_level: state.configuration?.requiredAccess ?? "read table",
|
|
3120
|
+
...state.widgetInteraction
|
|
3121
|
+
}));
|
|
3122
|
+
async function pushRecord(rowId) {
|
|
3123
|
+
const mappings = resolveMappings(state);
|
|
3124
|
+
if (rowId === "new") {
|
|
3125
|
+
lastNewRecord = mappings;
|
|
3126
|
+
for (const cb of newRecordCbs) {
|
|
3127
|
+
try {
|
|
3128
|
+
cb(mappings);
|
|
3129
|
+
} catch (err) {
|
|
3130
|
+
logErr(err);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
if (rowId === null) {
|
|
3136
|
+
lastRecord = { record: null, mappings };
|
|
3137
|
+
for (const [cb] of recordCbs) {
|
|
3138
|
+
try {
|
|
3139
|
+
cb(null, mappings);
|
|
3140
|
+
} catch (err) {
|
|
3141
|
+
logErr(err);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
let record = null;
|
|
3147
|
+
try {
|
|
3148
|
+
record = await api.GristView.fetchSelectedRecord(rowId);
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
logErr(err);
|
|
3151
|
+
}
|
|
3152
|
+
lastRecord = { record, mappings };
|
|
3153
|
+
for (const [cb, opts] of recordCbs) {
|
|
3154
|
+
try {
|
|
3155
|
+
const r = opts?.keepEncoded ? await api.GristView.fetchSelectedRecord(rowId, { keepEncoded: true }) : record;
|
|
3156
|
+
cb(r, mappings);
|
|
3157
|
+
} catch (err) {
|
|
3158
|
+
logErr(err);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
async function pushRecords() {
|
|
3163
|
+
const mappings = resolveMappings(state);
|
|
3164
|
+
let baseRows = [];
|
|
3165
|
+
try {
|
|
3166
|
+
const columnar = await api.GristView.fetchSelectedTable({});
|
|
3167
|
+
baseRows = columnsToRows(columnar);
|
|
3168
|
+
} catch (err) {
|
|
3169
|
+
logErr(err);
|
|
3170
|
+
}
|
|
3171
|
+
lastRecords = { records: baseRows, mappings };
|
|
3172
|
+
for (const [cb, opts] of recordsCbs) {
|
|
3173
|
+
try {
|
|
3174
|
+
const fetchOptions = coerceFetchOptions(opts);
|
|
3175
|
+
const rows = fetchOptions?.keepEncoded ? columnsToRows(await api.GristView.fetchSelectedTable({ keepEncoded: true })) : baseRows;
|
|
3176
|
+
cb(rows, mappings);
|
|
3177
|
+
} catch (err) {
|
|
3178
|
+
logErr(err);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
async function fetchSelectedTableShaped(opts) {
|
|
3183
|
+
const fetchOptions = coerceFetchOptions(opts);
|
|
3184
|
+
const columnar = await api.GristView.fetchSelectedTable(
|
|
3185
|
+
fetchOptions ? { keepEncoded: fetchOptions.keepEncoded } : {}
|
|
3186
|
+
);
|
|
3187
|
+
if (fetchOptions?.format === "rows") return columnsToRows(columnar);
|
|
3188
|
+
return columnar;
|
|
3189
|
+
}
|
|
3190
|
+
function pushOptions() {
|
|
3191
|
+
const opts = Object.keys(state.widgetOptions).length > 0 ? { ...state.widgetOptions } : null;
|
|
3192
|
+
const interaction = buildInteraction();
|
|
3193
|
+
lastOptions = { options: opts, interaction };
|
|
3194
|
+
for (const cb of optionsCbs) {
|
|
3195
|
+
try {
|
|
3196
|
+
cb(opts, interaction);
|
|
3197
|
+
} catch (err) {
|
|
3198
|
+
logErr(err);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
const offBus = bus.on(async (event) => {
|
|
3203
|
+
switch (event.type) {
|
|
3204
|
+
case "data-change":
|
|
3205
|
+
await pushRecords();
|
|
3206
|
+
await pushRecord(state.selection.rowId);
|
|
3207
|
+
break;
|
|
3208
|
+
case "cursor-change":
|
|
3209
|
+
await pushRecord(event.rowId);
|
|
3210
|
+
break;
|
|
3211
|
+
case "options-change":
|
|
3212
|
+
pushOptions();
|
|
3213
|
+
break;
|
|
3214
|
+
case "ready":
|
|
3215
|
+
pushOptions();
|
|
3216
|
+
if (options.initialTheme) {
|
|
3217
|
+
notify("message", buildGristThemeHostMessage(options.initialTheme, { fromReady: true }));
|
|
3218
|
+
}
|
|
3219
|
+
await pushRecords();
|
|
3220
|
+
await pushRecord(state.selection.rowId);
|
|
3221
|
+
break;
|
|
3222
|
+
case "mappings-change":
|
|
3223
|
+
await pushRecords();
|
|
3224
|
+
await pushRecord(state.selection.rowId);
|
|
3225
|
+
break;
|
|
3226
|
+
case "theme-change":
|
|
3227
|
+
notify("message", event.message);
|
|
3228
|
+
break;
|
|
3229
|
+
}
|
|
3230
|
+
});
|
|
3231
|
+
function makeTableOps(tableId) {
|
|
3232
|
+
return {
|
|
3233
|
+
async getTableId() {
|
|
3234
|
+
return tableId;
|
|
3235
|
+
},
|
|
3236
|
+
async create(records, options2) {
|
|
3237
|
+
const list = Array.isArray(records) ? records : [records];
|
|
3238
|
+
const ids = [];
|
|
3239
|
+
for (const r of list) {
|
|
3240
|
+
const result = applyUserActions(state, bus, [
|
|
3241
|
+
gristAddRecordAction(tableId, parseFields(r.fields, options2))
|
|
3242
|
+
]);
|
|
3243
|
+
ids.push({ id: result.retValues[0] });
|
|
3244
|
+
}
|
|
3245
|
+
return Array.isArray(records) ? ids : ids[0];
|
|
3246
|
+
},
|
|
3247
|
+
async update(records, options2) {
|
|
3248
|
+
const list = Array.isArray(records) ? records : [records];
|
|
3249
|
+
const actions = list.map((r) => gristUpdateRecordAction(tableId, r.id, parseFields(r.fields, options2)));
|
|
3250
|
+
applyUserActions(state, bus, actions);
|
|
3251
|
+
},
|
|
3252
|
+
async upsert(records, options2) {
|
|
3253
|
+
const list = Array.isArray(records) ? records : [records];
|
|
3254
|
+
const allowAdd = options2?.add !== false;
|
|
3255
|
+
const allowUpdate = options2?.update !== false;
|
|
3256
|
+
const table = getTable(state, tableId);
|
|
3257
|
+
const actions = [];
|
|
3258
|
+
for (const r of list) {
|
|
3259
|
+
const matches = table.rows.filter(
|
|
3260
|
+
(row) => Object.entries(r.require).every(([k, v]) => row[k] === v)
|
|
3261
|
+
);
|
|
3262
|
+
if (matches.length > 0) {
|
|
3263
|
+
if (!allowUpdate) continue;
|
|
3264
|
+
const fields = parseFields(r.fields ?? {}, options2);
|
|
3265
|
+
for (const m of matches) actions.push(gristUpdateRecordAction(tableId, m.id, fields));
|
|
3266
|
+
} else if (allowAdd) {
|
|
3267
|
+
actions.push(gristAddRecordAction(tableId, { ...r.require, ...parseFields(r.fields ?? {}, options2) }));
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
if (actions.length > 0) applyUserActions(state, bus, actions);
|
|
3271
|
+
},
|
|
3272
|
+
async destroy(recordIds) {
|
|
3273
|
+
if (Array.isArray(recordIds)) {
|
|
3274
|
+
applyUserActions(state, bus, [gristBulkRemoveRecordAction(tableId, recordIds)]);
|
|
3275
|
+
} else {
|
|
3276
|
+
applyUserActions(state, bus, [gristRemoveRecordAction(tableId, recordIds)]);
|
|
3277
|
+
}
|
|
3278
|
+
void gristBulkAddRecordAction;
|
|
3279
|
+
void gristBulkUpdateRecordAction;
|
|
3280
|
+
}
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
function notify(event, ...args) {
|
|
3284
|
+
const set = generic.get(event);
|
|
3285
|
+
if (!set) return;
|
|
3286
|
+
for (const fn of set) {
|
|
3287
|
+
try {
|
|
3288
|
+
fn(...args);
|
|
3289
|
+
} catch (err) {
|
|
3290
|
+
logErr(err);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
const grist2 = {
|
|
3295
|
+
async ready(opts) {
|
|
3296
|
+
await api.CustomSectionAPI.configure(opts ? { ...opts, onEditOptions: void 0 } : {});
|
|
3297
|
+
},
|
|
3298
|
+
onRecord(cb, opts) {
|
|
3299
|
+
recordCbs.set(cb, opts);
|
|
3300
|
+
if (lastRecord) {
|
|
3301
|
+
try {
|
|
3302
|
+
cb(lastRecord.record, lastRecord.mappings);
|
|
3303
|
+
} catch (err) {
|
|
3304
|
+
logErr(err);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
},
|
|
3308
|
+
onRecords(cb, opts) {
|
|
3309
|
+
recordsCbs.set(cb, opts);
|
|
3310
|
+
if (lastRecords) {
|
|
3311
|
+
try {
|
|
3312
|
+
cb(lastRecords.records, lastRecords.mappings);
|
|
3313
|
+
} catch (err) {
|
|
3314
|
+
logErr(err);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
},
|
|
3318
|
+
onOptions(cb) {
|
|
3319
|
+
optionsCbs.add(cb);
|
|
3320
|
+
if (lastOptions) {
|
|
3321
|
+
try {
|
|
3322
|
+
cb(lastOptions.options, lastOptions.interaction);
|
|
3323
|
+
} catch (err) {
|
|
3324
|
+
logErr(err);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
},
|
|
3328
|
+
onNewRecord(cb) {
|
|
3329
|
+
newRecordCbs.add(cb);
|
|
3330
|
+
if (lastNewRecord !== void 0) {
|
|
3331
|
+
try {
|
|
3332
|
+
cb(lastNewRecord);
|
|
3333
|
+
} catch (err) {
|
|
3334
|
+
logErr(err);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
},
|
|
3338
|
+
mapColumnNames(data) {
|
|
3339
|
+
const mappings = resolveMappings(state);
|
|
3340
|
+
if (!mappings) return null;
|
|
3341
|
+
const out = {};
|
|
3342
|
+
let allMapped = true;
|
|
3343
|
+
for (const [logical, real] of Object.entries(mappings)) {
|
|
3344
|
+
if (Array.isArray(real)) {
|
|
3345
|
+
out[logical] = real.map((r) => data[r]);
|
|
3346
|
+
} else if (typeof real === "string") {
|
|
3347
|
+
out[logical] = data[real];
|
|
3348
|
+
} else {
|
|
3349
|
+
allMapped = false;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
return allMapped ? out : null;
|
|
3353
|
+
},
|
|
3354
|
+
mapColumnNamesBack(data) {
|
|
3355
|
+
const mappings = resolveMappings(state);
|
|
3356
|
+
if (!mappings) return data;
|
|
3357
|
+
const out = {};
|
|
3358
|
+
for (const [logical, real] of Object.entries(mappings)) {
|
|
3359
|
+
if (typeof real === "string" && logical in data) {
|
|
3360
|
+
out[real] = data[logical];
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
return out;
|
|
3364
|
+
},
|
|
3365
|
+
getOption: (k) => api.WidgetAPI.getOption(k),
|
|
3366
|
+
setOption: (k, v) => api.WidgetAPI.setOption(k, v),
|
|
3367
|
+
getOptions: () => api.WidgetAPI.getOptions(),
|
|
3368
|
+
setOptions: (o) => api.WidgetAPI.setOptions(o),
|
|
3369
|
+
clearOptions: () => api.WidgetAPI.clearOptions(),
|
|
3370
|
+
setCursorPos: (pos) => api.GristView.setCursorPos(pos),
|
|
3371
|
+
setSelectedRows: (rowIds) => api.GristView.setSelectedRows(rowIds),
|
|
3372
|
+
fetchSelectedTable: (opts) => fetchSelectedTableShaped(opts),
|
|
3373
|
+
fetchSelectedRecord: (rowId, opts) => api.GristView.fetchSelectedRecord(rowId, coerceFetchOptions(opts)),
|
|
3374
|
+
docApi: {
|
|
3375
|
+
getDocName: () => api.GristDocAPI.getDocName(),
|
|
3376
|
+
listTables: () => api.GristDocAPI.listTables(),
|
|
3377
|
+
fetchTable: (id) => api.GristDocAPI.fetchTable(id),
|
|
3378
|
+
applyUserActions: (actions, options2) => api.GristDocAPI.applyUserActions(actions, options2),
|
|
3379
|
+
getAccessToken: (opts) => api.GristDocAPI.getAccessToken(opts),
|
|
3380
|
+
fetchSelectedTable: (opts) => fetchSelectedTableShaped(opts),
|
|
3381
|
+
fetchSelectedRecord: (rowId, opts) => api.GristView.fetchSelectedRecord(rowId, coerceFetchOptions(opts))
|
|
3382
|
+
},
|
|
3383
|
+
get selectedTable() {
|
|
3384
|
+
return makeTableOps(state.selection.tableId);
|
|
3385
|
+
},
|
|
3386
|
+
getTable(tableId) {
|
|
3387
|
+
return makeTableOps(tableId ?? state.selection.tableId);
|
|
3388
|
+
},
|
|
3389
|
+
sectionApi: {
|
|
3390
|
+
configure: (settings) => api.CustomSectionAPI.configure(settings),
|
|
3391
|
+
mappings: () => api.CustomSectionAPI.mappings()
|
|
3392
|
+
},
|
|
3393
|
+
on(eventName, listener) {
|
|
3394
|
+
let set = generic.get(eventName);
|
|
3395
|
+
if (!set) generic.set(eventName, set = /* @__PURE__ */ new Set());
|
|
3396
|
+
set.add(listener);
|
|
3397
|
+
return () => set.delete(listener);
|
|
3398
|
+
}
|
|
3399
|
+
};
|
|
3400
|
+
target.grist = grist2;
|
|
3401
|
+
return {
|
|
3402
|
+
grist: grist2,
|
|
3403
|
+
dispose() {
|
|
3404
|
+
offBus();
|
|
3405
|
+
const t = target;
|
|
3406
|
+
if (t.grist === grist2) delete t.grist;
|
|
3407
|
+
recordCbs.clear();
|
|
3408
|
+
recordsCbs.clear();
|
|
3409
|
+
optionsCbs.clear();
|
|
3410
|
+
newRecordCbs.clear();
|
|
3411
|
+
generic.clear();
|
|
3412
|
+
lastRecord = null;
|
|
3413
|
+
lastRecords = null;
|
|
3414
|
+
lastOptions = null;
|
|
3415
|
+
lastNewRecord = void 0;
|
|
3416
|
+
},
|
|
3417
|
+
fireDataChange(tableId) {
|
|
3418
|
+
bus.emit({ type: "data-change", tableId: tableId ?? state.selection.tableId });
|
|
3419
|
+
},
|
|
3420
|
+
fireCursor(rowId) {
|
|
3421
|
+
const id = rowId === void 0 ? state.selection.rowId : rowId;
|
|
3422
|
+
bus.emit({ type: "cursor-change", tableId: state.selection.tableId, rowId: id });
|
|
3423
|
+
},
|
|
3424
|
+
fireOptions() {
|
|
3425
|
+
pushOptions();
|
|
3426
|
+
},
|
|
3427
|
+
fireTheme(message2) {
|
|
3428
|
+
bus.emit({ type: "theme-change", message: message2 });
|
|
3429
|
+
},
|
|
3430
|
+
emitInitialState() {
|
|
3431
|
+
pushOptions();
|
|
3432
|
+
void pushRecords().then(() => pushRecord(state.selection.rowId));
|
|
3329
3433
|
}
|
|
3330
|
-
}
|
|
3331
|
-
const hint = options.sectionNotLinkedHint?.trim();
|
|
3332
|
-
out.push(
|
|
3333
|
-
alert(
|
|
3334
|
-
"section-not-linked",
|
|
3335
|
-
"This widget section is not linked to a table or selector. Link it from the page settings so the selected row is sent to the widget." + (hint ? ` ${hint}` : "")
|
|
3336
|
-
)
|
|
3337
|
-
);
|
|
3338
|
-
}
|
|
3339
|
-
const suppressed = widget.suppressAlerts ?? [];
|
|
3340
|
-
return out.filter((a) => !suppressed.includes(a.kind));
|
|
3434
|
+
};
|
|
3341
3435
|
}
|
|
3342
|
-
function
|
|
3343
|
-
const
|
|
3344
|
-
if (
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3436
|
+
function columnsToRows(columnar) {
|
|
3437
|
+
const cols = Object.keys(columnar);
|
|
3438
|
+
if (cols.length === 0) return [];
|
|
3439
|
+
const length = (columnar[cols[0]] ?? []).length;
|
|
3440
|
+
const rows = [];
|
|
3441
|
+
for (let i = 0; i < length; i++) {
|
|
3442
|
+
const row = {};
|
|
3443
|
+
for (const c of cols) row[c] = columnar[c][i];
|
|
3444
|
+
rows.push(row);
|
|
3445
|
+
}
|
|
3446
|
+
return rows;
|
|
3347
3447
|
}
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
3351
|
-
var ALERT_OPTIONS = {
|
|
3352
|
-
sectionNotLinkedHint: "In Grist, select this widget section on the page, then use Link to connect a table, card, or chart view as the selector."
|
|
3353
|
-
};
|
|
3354
|
-
function WidgetAlerts({ children }) {
|
|
3355
|
-
const alerts = useGristSdkAlerts(ALERT_OPTIONS);
|
|
3356
|
-
if (alerts.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
|
|
3357
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
|
|
3358
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", padding: "0.75rem 0.75rem 0" }, children: alerts.map((alert2) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WidgetAlert, { alert: alert2 }, alert2.id)) }),
|
|
3359
|
-
children
|
|
3360
|
-
] });
|
|
3448
|
+
function parseFields(fields, _options) {
|
|
3449
|
+
return { ...fields };
|
|
3361
3450
|
}
|
|
3362
|
-
function
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
3367
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { margin: 0, fontWeight: 500 }, children: alert2.title }),
|
|
3368
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { margin: "0.25rem 0 0", color: tokens.mutedForeground }, children: alert2.message })
|
|
3369
|
-
] })
|
|
3370
|
-
] });
|
|
3451
|
+
function coerceFetchOptions(opts) {
|
|
3452
|
+
if (!opts) return void 0;
|
|
3453
|
+
const format = opts.format === "rows" || opts.format === "columns" ? opts.format : void 0;
|
|
3454
|
+
return { keepEncoded: opts.keepEncoded, format };
|
|
3371
3455
|
}
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
display: "flex",
|
|
3375
|
-
gap: "0.75rem",
|
|
3376
|
-
padding: "0.75rem 1rem",
|
|
3377
|
-
border: `1px solid ${tokens.border}`,
|
|
3378
|
-
borderRadius: tokens.radius,
|
|
3379
|
-
color: tokens.foreground,
|
|
3380
|
-
fontSize: "0.875rem",
|
|
3381
|
-
lineHeight: 1.5
|
|
3382
|
-
};
|
|
3383
|
-
var SEVERITY2 = {
|
|
3384
|
-
error: { borderColor: tokens.destructive, color: tokens.destructive },
|
|
3385
|
-
warning: { borderColor: "rgb(245 158 11 / 0.4)", background: "rgb(245 158 11 / 0.1)" },
|
|
3386
|
-
info: { background: "transparent" }
|
|
3387
|
-
};
|
|
3388
|
-
var ICON = {
|
|
3389
|
-
width: 16,
|
|
3390
|
-
height: 16,
|
|
3391
|
-
viewBox: "0 0 24 24",
|
|
3392
|
-
fill: "none",
|
|
3393
|
-
stroke: "currentColor",
|
|
3394
|
-
strokeWidth: 2,
|
|
3395
|
-
strokeLinecap: "round",
|
|
3396
|
-
strokeLinejoin: "round"
|
|
3397
|
-
};
|
|
3398
|
-
function WarningIcon() {
|
|
3399
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { ...ICON, children: [
|
|
3400
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }),
|
|
3401
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 9v4" }),
|
|
3402
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 17h.01" })
|
|
3403
|
-
] });
|
|
3456
|
+
function logErr(err) {
|
|
3457
|
+
console.error("[grist-emulator/inline]", err);
|
|
3404
3458
|
}
|
|
3405
|
-
function
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3459
|
+
function installFakeIframeContext(target) {
|
|
3460
|
+
try {
|
|
3461
|
+
if (target.self !== target.top) return;
|
|
3462
|
+
Object.defineProperty(target, "top", {
|
|
3463
|
+
configurable: true,
|
|
3464
|
+
get: () => ({})
|
|
3465
|
+
});
|
|
3466
|
+
} catch {
|
|
3467
|
+
}
|
|
3411
3468
|
}
|
|
3412
3469
|
|
|
3413
|
-
// src/
|
|
3414
|
-
var
|
|
3415
|
-
var
|
|
3416
|
-
var
|
|
3417
|
-
var
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
)
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
)
|
|
3466
|
-
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
const
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
}, [active]);
|
|
3478
|
-
return theme;
|
|
3479
|
-
}
|
|
3480
|
-
function useApplyTheme(theme) {
|
|
3481
|
-
(0, import_react7.useEffect)(() => {
|
|
3482
|
-
const root = document.documentElement;
|
|
3483
|
-
if (root.classList.contains(theme) && !root.classList.contains(other(theme))) return;
|
|
3484
|
-
const restoreTransitions = suspendTransitions();
|
|
3485
|
-
root.classList.remove(other(theme));
|
|
3486
|
-
root.classList.add(theme);
|
|
3487
|
-
restoreTransitions();
|
|
3488
|
-
}, [theme]);
|
|
3489
|
-
}
|
|
3490
|
-
function systemTheme() {
|
|
3491
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "light";
|
|
3492
|
-
return window.matchMedia(DARK_QUERY).matches ? "dark" : "light";
|
|
3470
|
+
// src/emulator/validators.ts
|
|
3471
|
+
var import_zod = require("zod");
|
|
3472
|
+
var emulatorRowSchema = import_zod.z.object({ id: import_zod.z.number() }).catchall(import_zod.z.unknown());
|
|
3473
|
+
var formulaKindSchema = import_zod.z.enum(["computed", "trigger", "none", "empty_slot"]);
|
|
3474
|
+
var replicaColumnSchema = import_zod.z.object({
|
|
3475
|
+
colRef: import_zod.z.number().optional(),
|
|
3476
|
+
type: import_zod.z.string().optional(),
|
|
3477
|
+
label: import_zod.z.string().optional(),
|
|
3478
|
+
parentPos: import_zod.z.number().optional(),
|
|
3479
|
+
isFormula: import_zod.z.boolean().optional(),
|
|
3480
|
+
formulaKind: formulaKindSchema.optional(),
|
|
3481
|
+
formula: import_zod.z.string().optional(),
|
|
3482
|
+
description: import_zod.z.string().optional(),
|
|
3483
|
+
widgetOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
3484
|
+
displayColRef: import_zod.z.number().optional(),
|
|
3485
|
+
visibleColRef: import_zod.z.number().optional()
|
|
3486
|
+
}).strict();
|
|
3487
|
+
var replicaTableSchema = import_zod.z.object({
|
|
3488
|
+
label: import_zod.z.string().optional(),
|
|
3489
|
+
columns: import_zod.z.record(import_zod.z.string(), replicaColumnSchema).optional(),
|
|
3490
|
+
rows: import_zod.z.array(emulatorRowSchema).optional(),
|
|
3491
|
+
rowCount: import_zod.z.number().optional(),
|
|
3492
|
+
dataOmitted: import_zod.z.boolean().optional(),
|
|
3493
|
+
dataOmittedReason: import_zod.z.string().optional(),
|
|
3494
|
+
error: import_zod.z.string().optional()
|
|
3495
|
+
}).strict();
|
|
3496
|
+
var replicaSelectionSchema = import_zod.z.object({
|
|
3497
|
+
tableId: import_zod.z.string().optional(),
|
|
3498
|
+
rowId: import_zod.z.union([import_zod.z.number(), import_zod.z.literal("new"), import_zod.z.null()]).optional(),
|
|
3499
|
+
mappings: import_zod.z.unknown().nullable().optional(),
|
|
3500
|
+
cursor: import_zod.z.unknown().optional()
|
|
3501
|
+
}).strict();
|
|
3502
|
+
var replicaWidgetConfigurationSchema = import_zod.z.object({
|
|
3503
|
+
requiredAccess: import_zod.z.string().optional(),
|
|
3504
|
+
columns: import_zod.z.unknown().optional(),
|
|
3505
|
+
allowSelectBy: import_zod.z.boolean().optional(),
|
|
3506
|
+
hasCustomOptions: import_zod.z.boolean().optional()
|
|
3507
|
+
}).strict();
|
|
3508
|
+
var replicaAccessTokenSchema = import_zod.z.object({
|
|
3509
|
+
token: import_zod.z.string(),
|
|
3510
|
+
baseUrl: import_zod.z.string(),
|
|
3511
|
+
ttlMsecs: import_zod.z.number()
|
|
3512
|
+
}).strict();
|
|
3513
|
+
var replicaDocumentSchema = import_zod.z.object({
|
|
3514
|
+
generatedAt: import_zod.z.string(),
|
|
3515
|
+
source: import_zod.z.string().optional(),
|
|
3516
|
+
docName: import_zod.z.string().optional(),
|
|
3517
|
+
mode: import_zod.z.enum(["schema-only", "schema+samples", "schema+data"]).optional(),
|
|
3518
|
+
tables: import_zod.z.record(import_zod.z.string(), replicaTableSchema),
|
|
3519
|
+
selection: replicaSelectionSchema.optional(),
|
|
3520
|
+
widgetOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
3521
|
+
widgetInteraction: import_zod.z.unknown().optional(),
|
|
3522
|
+
configuration: replicaWidgetConfigurationSchema.optional(),
|
|
3523
|
+
accessToken: replicaAccessTokenSchema.nullable().optional()
|
|
3524
|
+
}).strict().refine((value) => Object.keys(value.tables).length > 0, {
|
|
3525
|
+
message: "at least one table is required",
|
|
3526
|
+
path: ["tables"]
|
|
3527
|
+
});
|
|
3528
|
+
function formatZodError(prefix, error) {
|
|
3529
|
+
const details = error.issues.map((issue) => {
|
|
3530
|
+
const where = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
3531
|
+
return `${where}: ${issue.message}`;
|
|
3532
|
+
}).join("; ");
|
|
3533
|
+
return new Error(`${prefix}: ${details}`);
|
|
3493
3534
|
}
|
|
3494
|
-
function
|
|
3495
|
-
return
|
|
3535
|
+
function safeParseDocument(input) {
|
|
3536
|
+
return replicaDocumentSchema.safeParse(input);
|
|
3496
3537
|
}
|
|
3497
|
-
function
|
|
3498
|
-
const
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
return () => {
|
|
3504
|
-
window.getComputedStyle(document.body);
|
|
3505
|
-
requestAnimationFrame(() => requestAnimationFrame(() => style.remove()));
|
|
3506
|
-
};
|
|
3538
|
+
function parseDocument(input) {
|
|
3539
|
+
const result = safeParseDocument(input);
|
|
3540
|
+
if (!result.success) {
|
|
3541
|
+
throw formatZodError("parseDocument", result.error);
|
|
3542
|
+
}
|
|
3543
|
+
return result.data;
|
|
3507
3544
|
}
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3545
|
+
|
|
3546
|
+
// src/emulator/create-grist-emulator.ts
|
|
3547
|
+
function createGristEmulator(options) {
|
|
3548
|
+
const validatedDocument = parseDocument(options.document);
|
|
3549
|
+
const state = createDocument({
|
|
3550
|
+
...validatedDocument,
|
|
3551
|
+
widgetOptions: options.widgetOptions ?? validatedDocument.widgetOptions,
|
|
3552
|
+
// No access_level by default: the emulator then grants the access the
|
|
3553
|
+
// widget asks for in grist.ready(), as a user accepting it in Grist would.
|
|
3554
|
+
widgetInteraction: options.widgetInteraction ?? validatedDocument.widgetInteraction ?? {}
|
|
3555
|
+
});
|
|
3556
|
+
const bus = createEventBus();
|
|
3557
|
+
const api = createApiBundle(state, bus);
|
|
3558
|
+
const inline = createInlineTransport({
|
|
3559
|
+
state,
|
|
3560
|
+
bus,
|
|
3561
|
+
api,
|
|
3562
|
+
initialTheme: options.initialTheme,
|
|
3563
|
+
target: options.transport?.target
|
|
3564
|
+
});
|
|
3565
|
+
const transport = inline;
|
|
3566
|
+
const grist2 = inline.grist;
|
|
3567
|
+
function apply(actions) {
|
|
3568
|
+
return applyUserActions(state, bus, actions);
|
|
3569
|
+
}
|
|
3570
|
+
return {
|
|
3571
|
+
state,
|
|
3572
|
+
bus,
|
|
3573
|
+
api,
|
|
3574
|
+
transport,
|
|
3575
|
+
grist: grist2,
|
|
3576
|
+
apply,
|
|
3577
|
+
mutate: {
|
|
3578
|
+
addRow: (tableId, fields) => apply([["AddRecord", tableId, null, fields]]).retValues[0],
|
|
3579
|
+
updateRow: (tableId, rowId, fields) => {
|
|
3580
|
+
apply([["UpdateRecord", tableId, rowId, fields]]);
|
|
3581
|
+
},
|
|
3582
|
+
removeRow: (tableId, rowId) => {
|
|
3583
|
+
apply([["RemoveRecord", tableId, rowId]]);
|
|
3584
|
+
}
|
|
3585
|
+
},
|
|
3586
|
+
selectRow(rowId) {
|
|
3587
|
+
state.selection.rowId = rowId;
|
|
3588
|
+
bus.emit({ type: "cursor-change", tableId: state.selection.tableId, rowId });
|
|
3589
|
+
},
|
|
3590
|
+
selectTable(tableId) {
|
|
3591
|
+
if (!state.tables.has(tableId)) throw new Error(`Unknown table: ${tableId}`);
|
|
3592
|
+
state.selection.tableId = tableId;
|
|
3593
|
+
const first = state.tables.get(tableId).rows[0]?.id ?? null;
|
|
3594
|
+
state.selection.rowId = first;
|
|
3595
|
+
bus.emit({ type: "data-change", tableId });
|
|
3596
|
+
bus.emit({ type: "cursor-change", tableId, rowId: first });
|
|
3597
|
+
},
|
|
3598
|
+
setColumnMappings(mappings) {
|
|
3599
|
+
if (mappings === void 0) {
|
|
3600
|
+
delete state.selection.mappings;
|
|
3601
|
+
} else {
|
|
3602
|
+
state.selection.mappings = mappings;
|
|
3603
|
+
}
|
|
3604
|
+
bus.emit({ type: "mappings-change", mappings: resolveMappings(state) });
|
|
3605
|
+
},
|
|
3606
|
+
async setOptions(opts) {
|
|
3607
|
+
await api.WidgetAPI.setOptions(opts);
|
|
3608
|
+
},
|
|
3609
|
+
setInteraction(interaction) {
|
|
3610
|
+
state.widgetInteraction = { ...interaction };
|
|
3611
|
+
bus.emit({ type: "options-change", options: { ...state.widgetOptions }, interaction: { ...interaction } });
|
|
3612
|
+
},
|
|
3613
|
+
theme: {
|
|
3614
|
+
set(next) {
|
|
3615
|
+
bus.emit({ type: "theme-change", message: buildGristThemeHostMessage(next) });
|
|
3616
|
+
}
|
|
3617
|
+
},
|
|
3618
|
+
dispose() {
|
|
3619
|
+
transport.dispose();
|
|
3620
|
+
bus.clear();
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3511
3623
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3624
|
+
|
|
3625
|
+
// src/emulator/templates.ts
|
|
3626
|
+
var PRESET_GENERATED_AT = "1970-01-01T00:00:00.000Z";
|
|
3627
|
+
function replicaBase(overrides) {
|
|
3628
|
+
return {
|
|
3629
|
+
generatedAt: PRESET_GENERATED_AT,
|
|
3630
|
+
...overrides
|
|
3631
|
+
};
|
|
3514
3632
|
}
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3633
|
+
var presets = {
|
|
3634
|
+
/**
|
|
3635
|
+
* Default starter fixture for demos / sandboxes that need a neutral document
|
|
3636
|
+
* without choosing a domain-specific preset.
|
|
3637
|
+
*/
|
|
3638
|
+
defaultTemplate() {
|
|
3639
|
+
return replicaBase({
|
|
3640
|
+
docName: "Default",
|
|
3641
|
+
tables: {
|
|
3642
|
+
Table1: {
|
|
3643
|
+
label: "Table1",
|
|
3644
|
+
rows: [
|
|
3645
|
+
{ id: 1, Name: "Alice" },
|
|
3646
|
+
{ id: 2, Name: "Bob" }
|
|
3647
|
+
]
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3651
|
+
},
|
|
3652
|
+
blank() {
|
|
3653
|
+
return replicaBase({
|
|
3654
|
+
docName: "Blank",
|
|
3655
|
+
tables: {
|
|
3656
|
+
Table1: { rows: [{ id: 1 }] }
|
|
3657
|
+
}
|
|
3658
|
+
});
|
|
3659
|
+
},
|
|
3660
|
+
todoList() {
|
|
3661
|
+
return replicaBase({
|
|
3662
|
+
docName: "Todo list",
|
|
3663
|
+
mode: "schema+data",
|
|
3664
|
+
tables: {
|
|
3665
|
+
Tasks: {
|
|
3666
|
+
label: "Tasks",
|
|
3667
|
+
columns: {
|
|
3668
|
+
title: { type: "Text", label: "Title" },
|
|
3669
|
+
done: { type: "Bool", label: "Done" },
|
|
3670
|
+
priority: { type: "Choice", label: "Priority" }
|
|
3671
|
+
},
|
|
3672
|
+
rows: [
|
|
3673
|
+
{ id: 1, title: "Buy milk", done: false, priority: "high" },
|
|
3674
|
+
{ id: 2, title: "Write the SOTA emulator", done: true, priority: "high" },
|
|
3675
|
+
{ id: 3, title: "Ship it", done: false, priority: "medium" }
|
|
3676
|
+
]
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
});
|
|
3680
|
+
},
|
|
3681
|
+
contacts() {
|
|
3682
|
+
return replicaBase({
|
|
3683
|
+
docName: "Contacts",
|
|
3684
|
+
mode: "schema+data",
|
|
3685
|
+
tables: {
|
|
3686
|
+
Contacts: {
|
|
3687
|
+
label: "Contacts",
|
|
3688
|
+
columns: {
|
|
3689
|
+
firstName: { type: "Text" },
|
|
3690
|
+
lastName: { type: "Text" },
|
|
3691
|
+
email: { type: "Text" },
|
|
3692
|
+
company: { type: "Text" }
|
|
3693
|
+
},
|
|
3694
|
+
rows: [
|
|
3695
|
+
{ id: 1, firstName: "Ada", lastName: "Lovelace", email: "ada@analytical.engine", company: "British Crown" },
|
|
3696
|
+
{ id: 2, firstName: "Linus", lastName: "Torvalds", email: "linus@kernel.org", company: "Linux Foundation" },
|
|
3697
|
+
{ id: 3, firstName: "Margaret", lastName: "Hamilton", email: "margaret@apollo.nasa", company: "NASA" }
|
|
3698
|
+
]
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
});
|
|
3521
3702
|
}
|
|
3703
|
+
};
|
|
3704
|
+
|
|
3705
|
+
// src/emulator/testing/render-with-grist.tsx
|
|
3706
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3707
|
+
function renderWithGrist(ui, options = {}) {
|
|
3708
|
+
fakeIframeContext();
|
|
3709
|
+
resetGristForTests();
|
|
3710
|
+
const widgetOptions = options.widget ? isGristWidget(options.widget) ? options.widget.options : options.widget : void 0;
|
|
3711
|
+
const columns = widgetOptions ? declaredColumns(widgetOptions) : [];
|
|
3712
|
+
const emulatorOptions = widgetOptions && !options.emulator?.document ? {
|
|
3713
|
+
...options.emulator,
|
|
3714
|
+
document: declaredDocument(columns, options.rows),
|
|
3715
|
+
widgetInteraction: options.emulator?.widgetInteraction ?? { access_level: widgetOptions.requiredAccess ?? "read table" }
|
|
3716
|
+
} : options.emulator ?? { document: presets.blank() };
|
|
3717
|
+
const emulator = options.existing ?? createGristEmulator(emulatorOptions);
|
|
3718
|
+
if (widgetOptions && columns.length > 0) emulator.setColumnMappings(mapEveryColumn(columns));
|
|
3719
|
+
const Widget = () => ui;
|
|
3720
|
+
const result = (0, import_react10.render)(widgetOptions ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(GristWidgetApp, { App: Widget, options: widgetOptions }) : ui, options);
|
|
3721
|
+
const originalUnmount = result.unmount;
|
|
3722
|
+
result.unmount = () => {
|
|
3723
|
+
originalUnmount();
|
|
3724
|
+
if (!options.existing) emulator.dispose();
|
|
3725
|
+
resetGristForTests();
|
|
3726
|
+
};
|
|
3727
|
+
return Object.assign(result, { emulator });
|
|
3522
3728
|
}
|
|
3523
|
-
function
|
|
3729
|
+
function fakeIframeContext() {
|
|
3730
|
+
if (typeof window === "undefined") return;
|
|
3731
|
+
if (window.self !== window.top) return;
|
|
3524
3732
|
try {
|
|
3525
|
-
|
|
3733
|
+
Object.defineProperty(window, "top", {
|
|
3734
|
+
configurable: true,
|
|
3735
|
+
get: () => ({})
|
|
3736
|
+
});
|
|
3526
3737
|
} catch {
|
|
3527
3738
|
}
|
|
3528
3739
|
}
|
|
3529
|
-
function noop() {
|
|
3530
|
-
}
|
|
3531
3740
|
|
|
3532
|
-
// src/
|
|
3533
|
-
var
|
|
3534
|
-
|
|
3535
|
-
App,
|
|
3536
|
-
options,
|
|
3537
|
-
dev = false,
|
|
3538
|
-
Alerts = WidgetAlerts,
|
|
3539
|
-
LocalDevPage: LocalDevPage2 = LocalDevPage,
|
|
3540
|
-
ReleasePage: ReleasePage2 = ReleasePage
|
|
3541
|
-
}) {
|
|
3542
|
-
const Wrap = Alerts ?? PassThrough;
|
|
3543
|
-
if (isEmbedded2()) {
|
|
3544
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristWidgetProvider, { options, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EmbeddedThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GristBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Wrap, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react8.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(App, {}) }) }) }) }) }) });
|
|
3545
|
-
}
|
|
3546
|
-
const channel = parseDeployPath(window.location.pathname).channel;
|
|
3547
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StandaloneThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: FILL, children: dev ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocalDevPage2, {}) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ReleasePage2, { channel }) }) });
|
|
3548
|
-
}
|
|
3549
|
-
var FILL = { minHeight: "100%", width: "100%" };
|
|
3550
|
-
function isEmbedded2() {
|
|
3551
|
-
return typeof window !== "undefined" && window.self !== window.top;
|
|
3552
|
-
}
|
|
3553
|
-
function PassThrough({ children }) {
|
|
3554
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
|
|
3555
|
-
}
|
|
3741
|
+
// src/emulator/testing/smoke-test-widget.tsx
|
|
3742
|
+
var import_react11 = require("react");
|
|
3743
|
+
var import_react12 = require("@testing-library/react");
|
|
3556
3744
|
|
|
3557
3745
|
// src/emulator/testing/jsdom-stubs.ts
|
|
3558
3746
|
function installJsdomStubs() {
|
|
@@ -3582,21 +3770,19 @@ function installJsdomStubs() {
|
|
|
3582
3770
|
}
|
|
3583
3771
|
|
|
3584
3772
|
// src/emulator/testing/smoke-test-widget.tsx
|
|
3585
|
-
var
|
|
3586
|
-
async function smokeTestWidget({
|
|
3587
|
-
App,
|
|
3588
|
-
options,
|
|
3589
|
-
document: document2,
|
|
3590
|
-
timeout = 1e4
|
|
3591
|
-
}) {
|
|
3773
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
3774
|
+
async function smokeTestWidget(input, extra = {}) {
|
|
3775
|
+
const { App, options: given, widget, document: document2, timeout = 1e4 } = "App" in input ? input : { ...entryProps(input), ...extra };
|
|
3592
3776
|
installJsdomStubs();
|
|
3777
|
+
const options = given ?? widget?.options;
|
|
3778
|
+
if (!options) throw new Error("smokeTestWidget: pass the widget's `widget` (defineGristWidget) or its `options` (GRIST_OPTIONS).");
|
|
3593
3779
|
const crash = { error: void 0 };
|
|
3594
3780
|
function Probe() {
|
|
3595
|
-
return /* @__PURE__ */ (0,
|
|
3781
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CatchRenderError, { crash, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-grist-smoke-test": "", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(App, {}) }) });
|
|
3596
3782
|
}
|
|
3597
3783
|
const columns = declaredColumns(options);
|
|
3598
|
-
const gristDocument = document2 ??
|
|
3599
|
-
const result = renderWithGrist(/* @__PURE__ */ (0,
|
|
3784
|
+
const gristDocument = document2 ?? declaredDocument(columns);
|
|
3785
|
+
const result = renderWithGrist(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GristWidgetApp, { App: Probe, options }), {
|
|
3600
3786
|
emulator: {
|
|
3601
3787
|
document: gristDocument,
|
|
3602
3788
|
// Granted as the user would in Grist's widget panel: the access the
|
|
@@ -3607,7 +3793,7 @@ async function smokeTestWidget({
|
|
|
3607
3793
|
try {
|
|
3608
3794
|
if (columns.length > 0) result.emulator.setColumnMappings(mapEveryColumn(columns));
|
|
3609
3795
|
try {
|
|
3610
|
-
await (0,
|
|
3796
|
+
await (0, import_react12.waitFor)(
|
|
3611
3797
|
() => {
|
|
3612
3798
|
if (crash.error !== void 0) return;
|
|
3613
3799
|
const probe = result.container.querySelector("[data-grist-smoke-test]");
|
|
@@ -3626,7 +3812,7 @@ async function smokeTestWidget({
|
|
|
3626
3812
|
result.unmount();
|
|
3627
3813
|
}
|
|
3628
3814
|
}
|
|
3629
|
-
var CatchRenderError = class extends
|
|
3815
|
+
var CatchRenderError = class extends import_react11.Component {
|
|
3630
3816
|
constructor() {
|
|
3631
3817
|
super(...arguments);
|
|
3632
3818
|
this.state = { failed: false };
|
|
@@ -3641,45 +3827,6 @@ var CatchRenderError = class extends import_react9.Component {
|
|
|
3641
3827
|
return this.state.failed ? null : this.props.children;
|
|
3642
3828
|
}
|
|
3643
3829
|
};
|
|
3644
|
-
function declaredColumns(options) {
|
|
3645
|
-
return (options.columns ?? []).map(
|
|
3646
|
-
(column) => typeof column === "string" ? { name: column, type: "Any", allowMultiple: false } : { name: column.name, type: firstType(column.type), allowMultiple: column.allowMultiple === true }
|
|
3647
|
-
);
|
|
3648
|
-
}
|
|
3649
|
-
function firstType(type) {
|
|
3650
|
-
return (type ?? "Any").split(",")[0].trim() || "Any";
|
|
3651
|
-
}
|
|
3652
|
-
function mapEveryColumn(columns) {
|
|
3653
|
-
return Object.fromEntries(columns.map((c) => [c.name, c.allowMultiple ? [c.name] : c.name]));
|
|
3654
|
-
}
|
|
3655
|
-
function smokeTestDocument(columns) {
|
|
3656
|
-
return {
|
|
3657
|
-
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
3658
|
-
docName: "Smoke test",
|
|
3659
|
-
mode: "schema+data",
|
|
3660
|
-
tables: {
|
|
3661
|
-
Table1: {
|
|
3662
|
-
columns: Object.fromEntries(columns.map((c) => [c.name, { type: c.type }])),
|
|
3663
|
-
rows: [{ id: 1, ...Object.fromEntries(columns.map((c) => [c.name, sampleValue(c.type)])) }]
|
|
3664
|
-
}
|
|
3665
|
-
}
|
|
3666
|
-
};
|
|
3667
|
-
}
|
|
3668
|
-
function sampleValue(type) {
|
|
3669
|
-
switch (type) {
|
|
3670
|
-
case "Numeric":
|
|
3671
|
-
case "Int":
|
|
3672
|
-
return 42;
|
|
3673
|
-
case "Bool":
|
|
3674
|
-
return true;
|
|
3675
|
-
case "Date":
|
|
3676
|
-
case "DateTime":
|
|
3677
|
-
return 1767225600;
|
|
3678
|
-
// 2026-01-01, as Grist stores it: epoch seconds
|
|
3679
|
-
default:
|
|
3680
|
-
return "Sample";
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
3830
|
|
|
3684
3831
|
// src/emulator/testing/matchers.ts
|
|
3685
3832
|
function actionsOf(emulator) {
|
|
@@ -3735,7 +3882,7 @@ function waitForEvent(emulator, kindOrPredicate, timeoutOrOptions = 1e3) {
|
|
|
3735
3882
|
}
|
|
3736
3883
|
|
|
3737
3884
|
// src/emulator/testing/index.ts
|
|
3738
|
-
var
|
|
3885
|
+
var import_react13 = require("@testing-library/react");
|
|
3739
3886
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3740
3887
|
0 && (module.exports = {
|
|
3741
3888
|
act,
|