@ai-matrx/records-ui 0.60.0 → 0.61.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 +38 -0
- package/dist/index.cjs +171 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -4
- package/dist/index.d.ts +106 -4
- package/dist/index.js +171 -19
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1178,8 +1178,13 @@ var LabelContext = createContext3({
|
|
|
1178
1178
|
// provides one, so this is only ever a bare unit test.
|
|
1179
1179
|
lookup: () => null,
|
|
1180
1180
|
want: () => {
|
|
1181
|
-
}
|
|
1181
|
+
},
|
|
1182
|
+
waiting: () => null
|
|
1182
1183
|
});
|
|
1184
|
+
var SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS = 1e3;
|
|
1185
|
+
function waitingLineFor(field, target) {
|
|
1186
|
+
return field.type === "list" ? "Reading this column\u2019s list of choices\u2026" : `Reading the ${target ?? "records"} this column points at\u2026`;
|
|
1187
|
+
}
|
|
1183
1188
|
function idsOf(value) {
|
|
1184
1189
|
if (Array.isArray(value)) return value.map(String).filter(isId);
|
|
1185
1190
|
if (typeof value === "string" && isId(value)) return [value];
|
|
@@ -1196,13 +1201,48 @@ function RecordLabelProvider({ children }) {
|
|
|
1196
1201
|
const client = useRecordsClient5();
|
|
1197
1202
|
const [names, setNames] = useState5({});
|
|
1198
1203
|
const [settled, setSettled] = useState5({});
|
|
1204
|
+
const [waitingLines, setWaitingLines] = useState5({});
|
|
1199
1205
|
const asked = useRef2(/* @__PURE__ */ new Set());
|
|
1200
1206
|
const chased = useRef2(/* @__PURE__ */ new Set());
|
|
1207
|
+
const timers = useRef2(/* @__PURE__ */ new Map());
|
|
1208
|
+
const startWaitClock = useCallback3((field, target) => {
|
|
1209
|
+
if (timers.current.has(field.id)) return;
|
|
1210
|
+
timers.current.set(
|
|
1211
|
+
field.id,
|
|
1212
|
+
setTimeout(() => {
|
|
1213
|
+
timers.current.delete(field.id);
|
|
1214
|
+
setWaitingLines(
|
|
1215
|
+
(current) => current[field.id] ? current : { ...current, [field.id]: waitingLineFor(field, target) }
|
|
1216
|
+
);
|
|
1217
|
+
}, SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS)
|
|
1218
|
+
);
|
|
1219
|
+
}, []);
|
|
1220
|
+
const stopWaitClock = useCallback3((fieldId) => {
|
|
1221
|
+
const timer = timers.current.get(fieldId);
|
|
1222
|
+
if (timer) {
|
|
1223
|
+
clearTimeout(timer);
|
|
1224
|
+
timers.current.delete(fieldId);
|
|
1225
|
+
}
|
|
1226
|
+
setWaitingLines((current) => {
|
|
1227
|
+
if (!current[fieldId]) return current;
|
|
1228
|
+
const next = { ...current };
|
|
1229
|
+
delete next[fieldId];
|
|
1230
|
+
return next;
|
|
1231
|
+
});
|
|
1232
|
+
}, []);
|
|
1233
|
+
useEffect5(
|
|
1234
|
+
() => () => {
|
|
1235
|
+
for (const timer of timers.current.values()) clearTimeout(timer);
|
|
1236
|
+
timers.current.clear();
|
|
1237
|
+
},
|
|
1238
|
+
[]
|
|
1239
|
+
);
|
|
1201
1240
|
const want = useCallback3(
|
|
1202
1241
|
(field) => {
|
|
1203
1242
|
if (!pointsAtRecords(field)) return;
|
|
1204
1243
|
if (asked.current.has(field.id)) return;
|
|
1205
1244
|
asked.current.add(field.id);
|
|
1245
|
+
startWaitClock(field, null);
|
|
1206
1246
|
void (async () => {
|
|
1207
1247
|
if (field.type === "list") {
|
|
1208
1248
|
const result = await client.fieldOptions({ field_id: field.id });
|
|
@@ -1216,11 +1256,13 @@ function RecordLabelProvider({ children }) {
|
|
|
1216
1256
|
});
|
|
1217
1257
|
}
|
|
1218
1258
|
setSettled((current) => ({ ...current, [field.id]: true }));
|
|
1259
|
+
stopWaitClock(field.id);
|
|
1219
1260
|
return;
|
|
1220
1261
|
}
|
|
1221
1262
|
const target = field.relation_target;
|
|
1222
1263
|
if (!target) {
|
|
1223
1264
|
setSettled((current) => ({ ...current, [field.id]: true }));
|
|
1265
|
+
stopWaitClock(field.id);
|
|
1224
1266
|
return;
|
|
1225
1267
|
}
|
|
1226
1268
|
const [rows, tables] = await Promise.all([
|
|
@@ -1238,24 +1280,54 @@ function RecordLabelProvider({ children }) {
|
|
|
1238
1280
|
});
|
|
1239
1281
|
}
|
|
1240
1282
|
setSettled((current) => ({ ...current, [field.id]: true }));
|
|
1283
|
+
stopWaitClock(field.id);
|
|
1241
1284
|
})();
|
|
1242
1285
|
},
|
|
1243
|
-
[client]
|
|
1286
|
+
[client, startWaitClock, stopWaitClock]
|
|
1244
1287
|
);
|
|
1288
|
+
const queued = useRef2(/* @__PURE__ */ new Map());
|
|
1289
|
+
const flushing = useRef2(null);
|
|
1290
|
+
const flush = useCallback3(() => {
|
|
1291
|
+
flushing.current = null;
|
|
1292
|
+
const batch = [...queued.current.entries()];
|
|
1293
|
+
queued.current.clear();
|
|
1294
|
+
if (batch.length === 0) return;
|
|
1295
|
+
void (async () => {
|
|
1296
|
+
const learned = {};
|
|
1297
|
+
const LANES = 6;
|
|
1298
|
+
let at = 0;
|
|
1299
|
+
await Promise.all(
|
|
1300
|
+
Array.from({ length: Math.min(LANES, batch.length) }, async () => {
|
|
1301
|
+
for (; ; ) {
|
|
1302
|
+
const next = batch[at];
|
|
1303
|
+
at += 1;
|
|
1304
|
+
if (!next) return;
|
|
1305
|
+
const [id] = next;
|
|
1306
|
+
const result = await client.recordRead({ record_id: id });
|
|
1307
|
+
learned[id] = result.ok ? recordName(result.data.document, null, "Untitled") : WITHHELD_RECORD_LABEL;
|
|
1308
|
+
}
|
|
1309
|
+
})
|
|
1310
|
+
);
|
|
1311
|
+
setNames((current) => ({ ...current, ...learned }));
|
|
1312
|
+
for (const [, field] of batch) stopWaitClock(field.id);
|
|
1313
|
+
})();
|
|
1314
|
+
}, [client, stopWaitClock]);
|
|
1245
1315
|
const chase = useCallback3(
|
|
1246
1316
|
(field, id) => {
|
|
1247
1317
|
const key = `${field.id}:${id}`;
|
|
1248
1318
|
if (chased.current.has(key)) return;
|
|
1249
1319
|
chased.current.add(key);
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
...current,
|
|
1254
|
-
[id]: result.ok ? recordName(result.data.document, null, "Untitled") : WITHHELD_RECORD_LABEL
|
|
1255
|
-
}));
|
|
1256
|
-
})();
|
|
1320
|
+
queued.current.set(id, field);
|
|
1321
|
+
startWaitClock(field, null);
|
|
1322
|
+
if (flushing.current === null) flushing.current = setTimeout(flush, 0);
|
|
1257
1323
|
},
|
|
1258
|
-
[
|
|
1324
|
+
[flush, startWaitClock]
|
|
1325
|
+
);
|
|
1326
|
+
useEffect5(
|
|
1327
|
+
() => () => {
|
|
1328
|
+
if (flushing.current !== null) clearTimeout(flushing.current);
|
|
1329
|
+
},
|
|
1330
|
+
[]
|
|
1259
1331
|
);
|
|
1260
1332
|
const lookup = useCallback3(
|
|
1261
1333
|
(field, id) => {
|
|
@@ -1266,7 +1338,8 @@ function RecordLabelProvider({ children }) {
|
|
|
1266
1338
|
},
|
|
1267
1339
|
[names, settled, chase]
|
|
1268
1340
|
);
|
|
1269
|
-
const
|
|
1341
|
+
const waiting = useCallback3((field) => waitingLines[field.id] ?? null, [waitingLines]);
|
|
1342
|
+
const value = useMemo5(() => ({ lookup, want, waiting }), [lookup, want, waiting]);
|
|
1270
1343
|
return /* @__PURE__ */ jsx7(LabelContext.Provider, { value, children });
|
|
1271
1344
|
}
|
|
1272
1345
|
function useRecordLabels(fields) {
|
|
@@ -1276,6 +1349,10 @@ function useRecordLabels(fields) {
|
|
|
1276
1349
|
}, [fields, want]);
|
|
1277
1350
|
return lookup;
|
|
1278
1351
|
}
|
|
1352
|
+
function useLabelWait(field) {
|
|
1353
|
+
const { waiting } = useContext3(LabelContext);
|
|
1354
|
+
return field ? waiting(field) : null;
|
|
1355
|
+
}
|
|
1279
1356
|
|
|
1280
1357
|
// src/values.tsx
|
|
1281
1358
|
import { Fragment as Fragment2, jsx as jsx8 } from "react/jsx-runtime";
|
|
@@ -1289,7 +1366,7 @@ var ABSENCE_WORDS = {
|
|
|
1289
1366
|
refused: "refused",
|
|
1290
1367
|
conflicting: "conflicting"
|
|
1291
1368
|
};
|
|
1292
|
-
function renderValue(field, value, document2, labels) {
|
|
1369
|
+
function renderValue(field, value, document2, labels, stillWaitingFor) {
|
|
1293
1370
|
const envelope = envelopeFor(document2, field.key);
|
|
1294
1371
|
if (envelope?.absent) {
|
|
1295
1372
|
return /* @__PURE__ */ jsx8("span", { className: "text-xs italic text-muted-foreground", title: `VAL-2: ${envelope.absent}`, children: ABSENCE_WORDS[String(envelope.absent)] ?? String(envelope.absent) });
|
|
@@ -1310,13 +1387,13 @@ function renderValue(field, value, document2, labels) {
|
|
|
1310
1387
|
if (typeof value === "boolean") {
|
|
1311
1388
|
return /* @__PURE__ */ jsx8("span", { className: kind === "checkbox" ? "text-sm" : "", title: value ? "Yes" : "No", children: value ? "\u2713" : "\u2014" });
|
|
1312
1389
|
}
|
|
1313
|
-
return /* @__PURE__ */ jsx8("span", { className: cn5("truncate", kind === "number" || kind === "currency" || kind === "percent" ? "tabular-nums" : ""), children: scalarText(field, value, labels) });
|
|
1390
|
+
return /* @__PURE__ */ jsx8("span", { className: cn5("truncate", kind === "number" || kind === "currency" || kind === "percent" ? "tabular-nums" : ""), children: scalarText(field, value, labels, stillWaitingFor) });
|
|
1314
1391
|
}
|
|
1315
|
-
function scalarText(field, value, labels) {
|
|
1392
|
+
function scalarText(field, value, labels, stillWaitingFor) {
|
|
1316
1393
|
if (value === null || value === void 0) return "\u2014";
|
|
1317
1394
|
const kind = editorKindFor(field);
|
|
1318
1395
|
if (isId(value) && (field.type === "list" || field.type === "relation")) {
|
|
1319
|
-
return labels?.(field, String(value)) ?? "Loading\u2026";
|
|
1396
|
+
return labels?.(field, String(value)) ?? stillWaitingFor ?? "Loading\u2026";
|
|
1320
1397
|
}
|
|
1321
1398
|
if (typeof value === "number") {
|
|
1322
1399
|
if (kind === "percent") return `${value}%`;
|
|
@@ -1348,9 +1425,10 @@ function RecordValue({
|
|
|
1348
1425
|
format
|
|
1349
1426
|
}) {
|
|
1350
1427
|
const labels = useRecordLabels(ONE_FIELD_ARRAY_CACHE(field));
|
|
1428
|
+
const stillWaitingFor = useLabelWait(field);
|
|
1351
1429
|
const formatted = format ? formatColumnValue({ format }, value) : null;
|
|
1352
1430
|
if (formatted) return /* @__PURE__ */ jsx8(FormattedCell, { result: formatted });
|
|
1353
|
-
return /* @__PURE__ */ jsx8(Fragment2, { children: renderValue(field, value, document2, labels) });
|
|
1431
|
+
return /* @__PURE__ */ jsx8(Fragment2, { children: renderValue(field, value, document2, labels, stillWaitingFor) });
|
|
1354
1432
|
}
|
|
1355
1433
|
var ONE_FIELD_ARRAYS = /* @__PURE__ */ new WeakMap();
|
|
1356
1434
|
function ONE_FIELD_ARRAY_CACHE(field) {
|
|
@@ -6214,6 +6292,37 @@ var LAYOUT_FIELD_KIND = {
|
|
|
6214
6292
|
calendar: "date",
|
|
6215
6293
|
gallery: null
|
|
6216
6294
|
};
|
|
6295
|
+
var PAGE_VIEWS = [...VIEW_LAYOUTS, "dashboards"];
|
|
6296
|
+
var PAGE_VIEW_LABEL = {
|
|
6297
|
+
...LAYOUT_LABEL,
|
|
6298
|
+
dashboards: "Dashboards"
|
|
6299
|
+
};
|
|
6300
|
+
var PAGE_VIEW_SPELLINGS = {
|
|
6301
|
+
grid: "grid",
|
|
6302
|
+
table: "grid",
|
|
6303
|
+
rows: "grid",
|
|
6304
|
+
list: "grid",
|
|
6305
|
+
kanban: "kanban",
|
|
6306
|
+
board: "kanban",
|
|
6307
|
+
pipeline: "kanban",
|
|
6308
|
+
calendar: "calendar",
|
|
6309
|
+
month: "calendar",
|
|
6310
|
+
schedule: "calendar",
|
|
6311
|
+
gallery: "gallery",
|
|
6312
|
+
cards: "gallery",
|
|
6313
|
+
dashboards: "dashboards",
|
|
6314
|
+
dashboard: "dashboards",
|
|
6315
|
+
charts: "dashboards"
|
|
6316
|
+
};
|
|
6317
|
+
function pageViewFromParam(raw) {
|
|
6318
|
+
if (typeof raw !== "string") return null;
|
|
6319
|
+
const token = raw.trim().toLowerCase();
|
|
6320
|
+
if (token === "") return null;
|
|
6321
|
+
return PAGE_VIEW_SPELLINGS[token] ?? null;
|
|
6322
|
+
}
|
|
6323
|
+
function unknownViewLine(raw) {
|
|
6324
|
+
return `The link asked for a view called \u201C${raw}\u201D, which this table has no view for, so you are looking at ${PAGE_VIEW_LABEL.grid}. The views this table has are ${PAGE_VIEWS.map((v) => PAGE_VIEW_LABEL[v]).join(", ")} \u2014 pick one above and the address follows it.`;
|
|
6325
|
+
}
|
|
6217
6326
|
var LAYOUT_NO_FIELD = {
|
|
6218
6327
|
grid: "",
|
|
6219
6328
|
kanban: "This table has no choice field yet, so there is nothing to make columns from. Add a field that holds one of a set of choices \u2014 Add field, then Select \u2014 and it appears here.",
|
|
@@ -14401,6 +14510,19 @@ function surfaceChosen(current, asking) {
|
|
|
14401
14510
|
function openingRail(activeRecordId) {
|
|
14402
14511
|
return activeRecordId ? { rail: "record", record: activeRecordId } : { rail: "none", record: null };
|
|
14403
14512
|
}
|
|
14513
|
+
function openingView(activeView, activeDashboardId) {
|
|
14514
|
+
const named = pageViewFromParam(activeView);
|
|
14515
|
+
if (named === null) {
|
|
14516
|
+
const asked = typeof activeView === "string" ? activeView.trim() : "";
|
|
14517
|
+
return {
|
|
14518
|
+
main: activeDashboardId ? "dashboards" : "records",
|
|
14519
|
+
layout: null,
|
|
14520
|
+
unknown: asked === "" ? null : asked
|
|
14521
|
+
};
|
|
14522
|
+
}
|
|
14523
|
+
if (named === "dashboards") return { main: "dashboards", layout: null, unknown: null };
|
|
14524
|
+
return { main: "records", layout: named, unknown: null };
|
|
14525
|
+
}
|
|
14404
14526
|
function TablePage({
|
|
14405
14527
|
tableId,
|
|
14406
14528
|
pageSize = 100,
|
|
@@ -14409,6 +14531,8 @@ function TablePage({
|
|
|
14409
14531
|
leaveLabel = "Back to your tables",
|
|
14410
14532
|
activeDashboardId,
|
|
14411
14533
|
activeRecordId,
|
|
14534
|
+
activeView,
|
|
14535
|
+
onViewChanged,
|
|
14412
14536
|
className
|
|
14413
14537
|
}) {
|
|
14414
14538
|
const client = useRecordsClient43();
|
|
@@ -14417,22 +14541,37 @@ function TablePage({
|
|
|
14417
14541
|
const organizationId = useRecordsClient43().config.organizationId;
|
|
14418
14542
|
const [view, setView] = useState52(null);
|
|
14419
14543
|
const opening = openingRail(activeRecordId);
|
|
14544
|
+
const opened = openingView(activeView, activeDashboardId);
|
|
14420
14545
|
const [asking, setAsking] = useState52(null);
|
|
14546
|
+
const [layoutFromLink, setLayoutFromLink] = useState52(opened.layout);
|
|
14421
14547
|
const [surface, setSurface] = useState52({
|
|
14422
|
-
main:
|
|
14548
|
+
main: opened.main,
|
|
14423
14549
|
rail: opening.rail
|
|
14424
14550
|
});
|
|
14425
14551
|
const { main, rail } = surface;
|
|
14426
14552
|
const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
|
|
14427
14553
|
const [openRecord, setOpenRecord] = useState52(opening.record);
|
|
14428
14554
|
const viewVersion = useRecordVersion(view?.id ?? null);
|
|
14429
|
-
const
|
|
14555
|
+
const shownLayout = layoutFromLink ?? view?.layout ?? "grid";
|
|
14556
|
+
const press = (pressed) => {
|
|
14557
|
+
const next = chooseSurface(surface, pressed);
|
|
14558
|
+
setSurface(next);
|
|
14559
|
+
if (next.main !== surface.main) {
|
|
14560
|
+
onViewChanged?.(next.main === "dashboards" ? "dashboards" : shownLayout);
|
|
14561
|
+
}
|
|
14562
|
+
};
|
|
14430
14563
|
const show = (next) => press({ rail: next });
|
|
14431
14564
|
useEffect41(() => {
|
|
14432
14565
|
if (!activeRecordId) return;
|
|
14433
14566
|
setOpenRecord(activeRecordId);
|
|
14434
14567
|
setSurface((now) => ({ ...now, rail: "record" }));
|
|
14435
14568
|
}, [activeRecordId]);
|
|
14569
|
+
useEffect41(() => {
|
|
14570
|
+
const named = pageViewFromParam(activeView);
|
|
14571
|
+
if (named === null) return;
|
|
14572
|
+
setLayoutFromLink(named === "dashboards" ? null : named);
|
|
14573
|
+
setSurface((now) => ({ ...now, main: named === "dashboards" ? "dashboards" : "records" }));
|
|
14574
|
+
}, [activeView]);
|
|
14436
14575
|
const patchView = useCallback37(
|
|
14437
14576
|
async (patch) => {
|
|
14438
14577
|
const current = view;
|
|
@@ -14586,11 +14725,16 @@ function TablePage({
|
|
|
14586
14725
|
/* @__PURE__ */ jsx57(ExportMenu, { tableId })
|
|
14587
14726
|
] }),
|
|
14588
14727
|
main === "dashboards" ? /* @__PURE__ */ jsx57(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : /* @__PURE__ */ jsxs52(Fragment27, { children: [
|
|
14728
|
+
opened.unknown ? /* @__PURE__ */ jsx57("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: unknownViewLine(opened.unknown) }) : null,
|
|
14589
14729
|
/* @__PURE__ */ jsx57(
|
|
14590
14730
|
ViewSwitcher,
|
|
14591
14731
|
{
|
|
14592
|
-
view: view ?? defaultView(tableId),
|
|
14732
|
+
view: { ...view ?? defaultView(tableId), layout: shownLayout },
|
|
14593
14733
|
pageSize,
|
|
14734
|
+
onLayoutChange: (layout) => {
|
|
14735
|
+
setLayoutFromLink(layout);
|
|
14736
|
+
onViewChanged?.(layout);
|
|
14737
|
+
},
|
|
14594
14738
|
...view ? { onViewChange: (patch) => void patchView(patch) } : {},
|
|
14595
14739
|
...rights.write ? { onNewRecordForm: () => setRail("new-record") } : {},
|
|
14596
14740
|
onOpenRecord: (recordId) => {
|
|
@@ -14766,6 +14910,8 @@ export {
|
|
|
14766
14910
|
NO_SHARE_REASON,
|
|
14767
14911
|
NO_UPLOAD_REASON,
|
|
14768
14912
|
NotifyRuleEditor,
|
|
14913
|
+
PAGE_VIEWS,
|
|
14914
|
+
PAGE_VIEW_LABEL,
|
|
14769
14915
|
PARITY_LABEL,
|
|
14770
14916
|
PARITY_MADE_OF,
|
|
14771
14917
|
Peek,
|
|
@@ -14789,6 +14935,7 @@ export {
|
|
|
14789
14935
|
RefusalNotice,
|
|
14790
14936
|
RelationPicker,
|
|
14791
14937
|
SAVED_VIEWS_UNAVAILABLE,
|
|
14938
|
+
SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
|
|
14792
14939
|
SERIES_COLORS,
|
|
14793
14940
|
SOMEBODY,
|
|
14794
14941
|
STORE_ANSWERS_THESE,
|
|
@@ -14859,6 +15006,9 @@ export {
|
|
|
14859
15006
|
machineIdentityIn,
|
|
14860
15007
|
memberName,
|
|
14861
15008
|
nextInWords,
|
|
15009
|
+
openingRail,
|
|
15010
|
+
openingView,
|
|
15011
|
+
pageViewFromParam,
|
|
14862
15012
|
parityTypesWithNoExplanation,
|
|
14863
15013
|
parseGridPresentation,
|
|
14864
15014
|
personActor,
|
|
@@ -14884,10 +15034,12 @@ export {
|
|
|
14884
15034
|
tableName,
|
|
14885
15035
|
tableRightsAt,
|
|
14886
15036
|
tokenFor,
|
|
15037
|
+
unknownViewLine,
|
|
14887
15038
|
useCanShare,
|
|
14888
15039
|
useEmbedHandshake,
|
|
14889
15040
|
useEnrichCells,
|
|
14890
15041
|
useGridEditing,
|
|
15042
|
+
useLabelWait,
|
|
14891
15043
|
useMeasuredWidth,
|
|
14892
15044
|
useMyChecklistSteps,
|
|
14893
15045
|
useRecordLabels,
|