@ai-matrx/records-ui 0.107.5 → 0.109.1

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.
@@ -0,0 +1,429 @@
1
+ import { recordingDataSource, asRecordsDataSource, createRecordsClient } from '@ai-matrx/records/core';
2
+ import { PARITY_FIELD_TYPES, KERNEL_TABLES, fieldKindFor } from '@ai-matrx/records';
3
+ import '@ai-matrx/kit/text-case';
4
+ import '@ai-matrx/design-system/data-table';
5
+ import { parseTableStyle, tableStyleIsEmpty } from '@ai-matrx/design-system/data-table/table-style';
6
+ import '@ai-matrx/design-system/field-formats';
7
+
8
+ // src/firstPage.ts
9
+ var kernelId = (name) => KERNEL_TABLES.find((t) => t.name === name)?.id ?? "";
10
+ var KERNEL_FILE_TABLE = kernelId("File");
11
+ var KERNEL_PERSON_TABLE = kernelId("Person");
12
+ Object.fromEntries(
13
+ PARITY_FIELD_TYPES.map((p) => [p.parity_type, p.made_of])
14
+ );
15
+ var KERNEL = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };
16
+ function editorKindFor(field) {
17
+ return fieldKindFor(field, KERNEL);
18
+ }
19
+
20
+ // src/gridLook.ts
21
+ function defaultSortOf(table, fields) {
22
+ const raw = table?.default_sort ?? null;
23
+ const first = Array.isArray(raw) ? raw[0] : raw;
24
+ let name = null;
25
+ let direction = "asc";
26
+ if (typeof first === "string") {
27
+ const trimmed = first.trim();
28
+ if (trimmed === "") return null;
29
+ if (trimmed.startsWith("-")) {
30
+ name = trimmed.slice(1).trim();
31
+ direction = "desc";
32
+ } else {
33
+ const [head, tail] = trimmed.split(/\s+/);
34
+ name = head ?? null;
35
+ if (tail && tail.toLowerCase() === "desc") direction = "desc";
36
+ }
37
+ } else if (first && typeof first === "object") {
38
+ const entry = first;
39
+ const named = entry["field"] ?? entry["key"] ?? entry["id"];
40
+ name = typeof named === "string" ? named : null;
41
+ if (String(entry["direction"] ?? "").toLowerCase() === "desc") direction = "desc";
42
+ }
43
+ if (!name) return null;
44
+ const field = fields.find((f) => f.key === name) ?? fields.find((f) => f.id === name);
45
+ return field ? { id: field.key, direction } : null;
46
+ }
47
+ function holdsNumbers(field) {
48
+ const kind = String(editorKindFor(field));
49
+ if (kind === "number" || kind === "currency" || kind === "percent") return true;
50
+ return String((field.config ?? {})["system"] ?? "") === "autonumber";
51
+ }
52
+ function sortAsFor(field) {
53
+ const kind = String(editorKindFor(field));
54
+ if (holdsNumbers(field)) return "number";
55
+ if (kind === "datetime") return "date";
56
+ return "text";
57
+ }
58
+ function pageSortFor(fields, sort) {
59
+ if (!sort) return [];
60
+ const field = fields.find((f) => f.key === sort.id);
61
+ if (!field) return [];
62
+ return [{ field: field.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field) }];
63
+ }
64
+ var MIN_ROW_HEIGHT = 24;
65
+ var MAX_ROW_HEIGHT = 96;
66
+ var AGGREGATE_KINDS = [
67
+ "count",
68
+ "sum",
69
+ "average",
70
+ "min",
71
+ "max",
72
+ "empty",
73
+ "filled",
74
+ "unique",
75
+ "checked",
76
+ "unchecked",
77
+ "percentFilled"
78
+ ];
79
+ var GROUP_ORDERS = [
80
+ "value-asc",
81
+ "value-desc",
82
+ "count-desc",
83
+ "first-seen"
84
+ ];
85
+ function isRecord(v) {
86
+ return typeof v === "object" && v !== null && !Array.isArray(v);
87
+ }
88
+ function parseGridPresentation(raw) {
89
+ let value = raw;
90
+ if (typeof value === "string") {
91
+ const trimmed = value.trim();
92
+ if (trimmed === "") return {};
93
+ try {
94
+ value = JSON.parse(trimmed);
95
+ } catch {
96
+ return {};
97
+ }
98
+ }
99
+ if (!isRecord(value)) return {};
100
+ const out = {};
101
+ const style = parseTableStyle(value["style"]);
102
+ if (!tableStyleIsEmpty(style)) out.style = style;
103
+ if (isRecord(value["formats"])) {
104
+ const formats = {};
105
+ for (const [key, cfg] of Object.entries(value["formats"])) {
106
+ if (!isRecord(cfg)) continue;
107
+ const id = cfg["id"];
108
+ if (typeof id !== "string") continue;
109
+ const options = cfg["options"];
110
+ formats[key] = isRecord(options) ? {
111
+ id,
112
+ options
113
+ } : { id };
114
+ }
115
+ if (Object.keys(formats).length > 0) out.formats = formats;
116
+ }
117
+ if (Array.isArray(value["frozen"])) {
118
+ const frozen = value["frozen"].filter((k) => typeof k === "string");
119
+ if (frozen.length > 0) out.frozen = frozen;
120
+ }
121
+ const grouping = parseGrouping(value["grouping"]);
122
+ if (grouping) out.grouping = grouping;
123
+ if (isRecord(value["widths"])) {
124
+ const widths = {};
125
+ for (const [key, raw2] of Object.entries(value["widths"])) {
126
+ if (typeof raw2 !== "number" || !Number.isFinite(raw2) || raw2 <= 0) continue;
127
+ widths[key] = Math.round(raw2);
128
+ }
129
+ if (Object.keys(widths).length > 0) out.widths = widths;
130
+ }
131
+ const rowHeight = value["rowHeight"];
132
+ if (typeof rowHeight === "number" && Number.isFinite(rowHeight) && rowHeight >= MIN_ROW_HEIGHT && rowHeight <= MAX_ROW_HEIGHT) {
133
+ out.rowHeight = Math.round(rowHeight);
134
+ }
135
+ if (Array.isArray(value["hiddenFields"])) {
136
+ const hiddenFields = value["hiddenFields"].filter((k) => typeof k === "string");
137
+ if (hiddenFields.length > 0) out.hiddenFields = hiddenFields;
138
+ }
139
+ if (Array.isArray(value["cardHidden"])) {
140
+ out.cardHidden = value["cardHidden"].filter((k) => typeof k === "string");
141
+ }
142
+ if (Array.isArray(value["columnOrder"])) {
143
+ const columnOrder = value["columnOrder"].filter((k) => typeof k === "string");
144
+ if (columnOrder.length > 0) out.columnOrder = columnOrder;
145
+ }
146
+ const gallerySize = value["gallerySize"];
147
+ if (gallerySize === "small" || gallerySize === "medium" || gallerySize === "large") {
148
+ out.gallerySize = gallerySize;
149
+ }
150
+ if (typeof value["wrap"] === "boolean") out.wrap = value["wrap"];
151
+ if (Array.isArray(value["wrapColumns"])) {
152
+ const wrapColumns = value["wrapColumns"].filter((k) => typeof k === "string");
153
+ if (wrapColumns.length > 0) out.wrapColumns = wrapColumns;
154
+ }
155
+ const fit = value["fit"];
156
+ if (fit === "auto" || fit === "fit" || fit === "scroll") out.fit = fit;
157
+ if (typeof value["freezeFirst"] === "boolean") out.freezeFirst = value["freezeFirst"];
158
+ const footer = value["footer"];
159
+ if (footer === "sticky" || footer === "inline") out.footer = footer;
160
+ if (isRecord(value["summaries"])) {
161
+ const summaries = {};
162
+ for (const [key, op] of Object.entries(value["summaries"])) {
163
+ if (typeof op === "string" && op.trim() !== "") summaries[key] = op;
164
+ }
165
+ if (Object.keys(summaries).length > 0) out.summaries = summaries;
166
+ }
167
+ return out;
168
+ }
169
+ function parseGrouping(raw) {
170
+ if (!isRecord(raw)) return void 0;
171
+ const field = raw["field"];
172
+ if (typeof field !== "string" || field.trim() === "") return void 0;
173
+ const out = { field };
174
+ if (isRecord(raw["aggregates"])) {
175
+ const aggregates = {};
176
+ for (const [key, spec] of Object.entries(raw["aggregates"])) {
177
+ if (!isRecord(spec)) continue;
178
+ const kind = spec["kind"];
179
+ if (typeof kind !== "string") continue;
180
+ if (!AGGREGATE_KINDS.includes(kind)) continue;
181
+ const label = spec["label"];
182
+ aggregates[key] = typeof label === "string" ? { kind, label } : { kind };
183
+ }
184
+ if (Object.keys(aggregates).length > 0) out.aggregates = aggregates;
185
+ }
186
+ const order = raw["order"];
187
+ if (typeof order === "string" && GROUP_ORDERS.includes(order)) {
188
+ out.order = order;
189
+ }
190
+ if (Array.isArray(raw["collapsed"])) {
191
+ const collapsed = raw["collapsed"].filter((k) => typeof k === "string");
192
+ if (collapsed.length > 0) out.collapsed = collapsed;
193
+ }
194
+ if (raw["showColumn"] === true) out.showColumn = true;
195
+ if (Array.isArray(raw["then"])) {
196
+ const then = [];
197
+ for (const level of raw["then"]) {
198
+ if (!isRecord(level)) continue;
199
+ const sub = level["field"];
200
+ if (typeof sub !== "string" || sub.trim() === "") continue;
201
+ const subOrder = level["order"];
202
+ const subGrain = level["grain"];
203
+ then.push({
204
+ field: sub,
205
+ ...typeof subOrder === "string" && GROUP_ORDERS.includes(subOrder) ? { order: subOrder } : {},
206
+ ...typeof subGrain === "string" && subGrain !== "" ? { grain: subGrain } : {}
207
+ });
208
+ }
209
+ if (then.length > 0) out.then = then.slice(0, 2);
210
+ }
211
+ const grain = raw["grain"];
212
+ if (typeof grain === "string" && grain !== "") out.grain = grain;
213
+ const across = raw["across"];
214
+ if (typeof across === "string" && across.trim() !== "") out.across = across;
215
+ if (Array.isArray(raw["show"])) {
216
+ const show = raw["show"].filter((k) => typeof k === "string" && k !== "");
217
+ if (show.length > 0) out.show = show;
218
+ }
219
+ if (Array.isArray(raw["trail"])) {
220
+ const trail = [];
221
+ for (const step of raw["trail"]) {
222
+ if (!isRecord(step) || typeof step["dim"] !== "string" || step["dim"] === "") continue;
223
+ const value = step["value"];
224
+ if (value !== null && typeof value !== "string") continue;
225
+ trail.push({ dim: step["dim"], value });
226
+ }
227
+ if (trail.length > 0) out.trail = trail;
228
+ }
229
+ return out;
230
+ }
231
+ var STORE_KEPT_LAYOUTS = ["grid", "kanban", "calendar", "gallery", ...["timeline"] , "list"];
232
+ var KINDS = STORE_KEPT_LAYOUTS;
233
+ function viewSettings(definition, rowFilters) {
234
+ const data = definition ?? {};
235
+ const text = (key) => {
236
+ const v = data[key];
237
+ return typeof v === "string" && v !== "" ? v : null;
238
+ };
239
+ const layout = String(data["layout"] ?? "grid");
240
+ const flat = rowFilters ?? (isObject(data["filters"]) ? data["filters"] : null);
241
+ const moved = data["moved_from"];
242
+ return {
243
+ layout: KINDS.includes(layout) ? layout : "grid",
244
+ filters: flat && Object.keys(flat).length > 0 ? flat : null,
245
+ where: isRule(data["where"]) ? data["where"] : null,
246
+ groupField: text("group_field"),
247
+ swimlaneField: text("swimlane_field"),
248
+ collapsedColumns: Array.isArray(data["collapsed_columns"]) ? data["collapsed_columns"].filter((v) => ["string", "number", "boolean"].includes(typeof v)).map(String) : [],
249
+ measure: text("measure"),
250
+ dateField: text("date_field"),
251
+ startField: text("start_field"),
252
+ endField: text("end_field"),
253
+ imageField: text("image_field"),
254
+ sorts: parseSorts(data["sorts"]),
255
+ ruleId: text("rule_id"),
256
+ isDefault: data["is_default"] === true || data["is_default"] === "true",
257
+ presentation: parseGridPresentation(data["presentation"]),
258
+ movedFrom: isObject(moved) ? moved : null
259
+ };
260
+ }
261
+ function isObject(raw) {
262
+ return Boolean(raw) && typeof raw === "object" && !Array.isArray(raw);
263
+ }
264
+ function isRule(raw) {
265
+ if (!isObject(raw)) return false;
266
+ const node = raw;
267
+ return typeof node.op === "string" && Array.isArray(node.args);
268
+ }
269
+ function parseSorts(raw) {
270
+ let list = raw;
271
+ if (typeof raw === "string") {
272
+ try {
273
+ list = raw.trim() === "" ? [] : JSON.parse(raw);
274
+ } catch {
275
+ return [];
276
+ }
277
+ }
278
+ if (!Array.isArray(list)) return [];
279
+ return list.filter((s) => isObject(s) && typeof s.field === "string").map((s) => ({ field: s.field, direction: s.direction === "desc" ? "desc" : "asc" }));
280
+ }
281
+
282
+ // src/views.ts
283
+ var VIEW_LAYOUTS = ["grid", "kanban", "calendar", "gallery", "timeline", "list"];
284
+ function viewOrderOf(definition) {
285
+ const raw = definition?.["order"];
286
+ return raw === "manual" ? "manual" : raw === "sorted" ? "sorted" : null;
287
+ }
288
+ function defaultViewOf(views) {
289
+ return views.find((v) => v.isDefault);
290
+ }
291
+ function viewFromRow(row) {
292
+ const data = row.definition ?? {};
293
+ const k = viewSettings(data, row.filters && Object.keys(row.filters).length > 0 ? row.filters : null);
294
+ const tableFromDefinition = typeof data["table_id"] === "string" && data["table_id"] !== "" ? data["table_id"] : null;
295
+ return {
296
+ id: row.view_id,
297
+ name: row.name || "Untitled view",
298
+ subject: row.table_id ?? tableFromDefinition ?? "",
299
+ layout: k.layout,
300
+ groupField: k.groupField,
301
+ swimlaneField: k.swimlaneField,
302
+ collapsedColumns: k.collapsedColumns,
303
+ measure: k.measure,
304
+ dateField: k.dateField,
305
+ startField: k.startField,
306
+ endField: k.endField,
307
+ imageField: k.imageField,
308
+ sorts: k.sorts,
309
+ order: viewOrderOf(data),
310
+ ruleId: k.ruleId,
311
+ ...k.filters ? { filters: k.filters } : {},
312
+ where: k.where,
313
+ // The older table's own default view, carried in by the mover, IS this table's default —
314
+ // the mover wrote the row's column, which `custom.views` does not return. Read as anything
315
+ // else, the page would seed a second "All records" beside it and open on that.
316
+ isDefault: k.isDefault || k.movedFrom?.["kind"] === "default_view",
317
+ presentation: k.presentation,
318
+ movedFrom: k.movedFrom
319
+ };
320
+ }
321
+
322
+ // src/viewLook.ts
323
+ function lookFromDocument(doc) {
324
+ const data = doc ?? {};
325
+ const read = viewSettings(data);
326
+ const out = {};
327
+ const has = (key) => Object.prototype.hasOwnProperty.call(data, key);
328
+ if (has("layout") && VIEW_LAYOUTS.includes(String(data["layout"]))) {
329
+ out.layout = data["layout"];
330
+ }
331
+ if (has("group_field")) out.groupField = read.groupField;
332
+ if (has("swimlane_field")) out.swimlaneField = read.swimlaneField;
333
+ if (has("collapsed_columns")) out.collapsedColumns = read.collapsedColumns;
334
+ if (has("measure")) out.measure = read.measure;
335
+ if (has("date_field")) out.dateField = read.dateField;
336
+ if (has("start_field")) out.startField = read.startField;
337
+ if (has("end_field")) out.endField = read.endField;
338
+ if (has("image_field")) out.imageField = read.imageField;
339
+ if (has("sorts")) out.sorts = read.sorts;
340
+ if (has("where")) out.where = read.where;
341
+ if (has("presentation")) out.presentation = parseGridPresentation(data["presentation"]);
342
+ return out;
343
+ }
344
+ function applyLook(view, look) {
345
+ if (!look) return view;
346
+ return { ...view, ...look };
347
+ }
348
+ async function readLooks(client, tableId) {
349
+ const answered = await client.viewLookRead({ table_id: tableId });
350
+ if (!answered.ok) return answered;
351
+ const out = {};
352
+ for (const [viewId, row] of Object.entries(answered.data ?? {})) {
353
+ out[viewId] = lookFromDocument(row?.look ?? null);
354
+ }
355
+ return { ok: true, data: out };
356
+ }
357
+
358
+ // src/firstPage.ts
359
+ var TABLE_PAGE_SIZE = 100;
360
+ var EMBEDDED_PAGE_SIZE = 50;
361
+ function firstGridPageArgs(input) {
362
+ const { shown, fields } = input;
363
+ if (!shown) return null;
364
+ if ((shown.layout ?? "grid") !== "grid") return null;
365
+ if (shown.ruleId) return null;
366
+ if (shown.order === "manual") return null;
367
+ if (shown.filters && Object.keys(shown.filters).length > 0) return null;
368
+ if (shown.presentation?.grouping) return null;
369
+ const first = (shown.sorts ?? [])[0];
370
+ const viewSort = first && fields.some((f) => f.key === first.field) ? { id: first.field, direction: first.direction } : null;
371
+ const opening = viewSort ?? defaultSortOf(input.table, fields);
372
+ return {
373
+ p_table_id: input.tableId,
374
+ p_filter: {},
375
+ p_search: null,
376
+ p_sort: pageSortFor(fields, opening),
377
+ p_view_id: null,
378
+ p_limit: input.pageSize ?? TABLE_PAGE_SIZE,
379
+ p_offset: 0
380
+ };
381
+ }
382
+ async function firstGridPageRequest(client, tableId, options = {}) {
383
+ const [table, fields, views, looks] = await Promise.all([
384
+ client.tableRead({ table_id: tableId }),
385
+ client.fields({ table_id: tableId }),
386
+ client.views({ table_id: tableId }),
387
+ readLooks(client, tableId)
388
+ ]);
389
+ if (!table.ok || !fields.ok || !views.ok || !looks.ok) return null;
390
+ const mine = views.data.map(viewFromRow).filter((v) => v.subject === tableId);
391
+ const view = defaultViewOf(mine) ?? mine[0] ?? null;
392
+ const shown = view ? applyLook(view, looks.data[view.id]) : null;
393
+ return firstGridPageArgs({ tableId, table: table.data, fields: fields.data, shown, ...options.pageSize ? { pageSize: options.pageSize } : {} });
394
+ }
395
+ async function askTablePageSeed(input) {
396
+ const recording = recordingDataSource(asRecordsDataSource(input.dataSource));
397
+ const client = createRecordsClient({
398
+ dataSource: recording.source,
399
+ organizationId: input.organizationId,
400
+ actor: input.actor,
401
+ onError: () => {
402
+ }
403
+ });
404
+ const record = input.recordId ? Promise.resolve(
405
+ recording.source.rpc("record_page_bundle", { p_organization_id: input.organizationId, p_record_id: input.recordId }, { schema: "custom" })
406
+ ).catch(() => null) : null;
407
+ try {
408
+ const pageSize = input.pageSize ?? (input.embedded ? EMBEDDED_PAGE_SIZE : TABLE_PAGE_SIZE);
409
+ const args = await firstGridPageRequest(client, input.tableId, { pageSize });
410
+ if (args && input.rows !== false) {
411
+ await client.listPage({
412
+ table_id: args.p_table_id,
413
+ filter: args.p_filter,
414
+ search: args.p_search,
415
+ sort: args.p_sort,
416
+ view_id: args.p_view_id,
417
+ limit: args.p_limit,
418
+ offset: args.p_offset
419
+ });
420
+ }
421
+ } catch {
422
+ }
423
+ if (record) await record;
424
+ return recording.seed();
425
+ }
426
+
427
+ export { EMBEDDED_PAGE_SIZE, TABLE_PAGE_SIZE, askTablePageSeed, firstGridPageArgs, firstGridPageRequest };
428
+ //# sourceMappingURL=first-page.js.map
429
+ //# sourceMappingURL=first-page.js.map