@ai-matrx/records-ui 0.110.4 → 0.110.5

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.
@@ -339,6 +339,12 @@ declare function firstGridPageArgs(input: {
339
339
  /** The view as THIS person sees it (the saved view with her look over it), or null for none. */
340
340
  shown: SavedView | null;
341
341
  pageSize?: number;
342
+ /**
343
+ * The host draws the MERGED grid (`RecordsUiProvider grid="merged"`), which keeps a plain grouping as rows
344
+ * under group headers: the page is then read in group order. The classic grid opens the drill for a
345
+ * grouped view, so a grouped view stays unseeded there.
346
+ */
347
+ merged?: boolean;
342
348
  }): FirstGridPageArgs | null;
343
349
  /**
344
350
  * Read what the screen reads (through `client`, e.g. one served from `custom.table_page_bundle`)
@@ -346,6 +352,7 @@ declare function firstGridPageArgs(input: {
346
352
  */
347
353
  declare function firstGridPageRequest(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
348
354
  pageSize?: number;
355
+ merged?: boolean;
349
356
  }): Promise<FirstGridPageArgs | null>;
350
357
  /**
351
358
  * A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON (lane SSR-ROWS).
@@ -389,6 +396,12 @@ declare function askTablePageSeed(input: {
389
396
  /** The table is drawn EMBEDDED (`TablePage presentation="embedded"`, a Space's inline table): its page size. */
390
397
  embedded?: boolean;
391
398
  pageSize?: number;
399
+ /**
400
+ * The host draws the merged grid. A view grouped by one field (no sub-levels, pivot or drill trail) is then
401
+ * seeded too: the page in group order, the group counts, the summary bar, and the words of the links on the
402
+ * page — each asked with the arguments the grid sends, so the browser's own call finds its answer.
403
+ */
404
+ merged?: boolean;
392
405
  }): Promise<RecordsSeed>;
393
406
 
394
407
  export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf };
@@ -339,6 +339,12 @@ declare function firstGridPageArgs(input: {
339
339
  /** The view as THIS person sees it (the saved view with her look over it), or null for none. */
340
340
  shown: SavedView | null;
341
341
  pageSize?: number;
342
+ /**
343
+ * The host draws the MERGED grid (`RecordsUiProvider grid="merged"`), which keeps a plain grouping as rows
344
+ * under group headers: the page is then read in group order. The classic grid opens the drill for a
345
+ * grouped view, so a grouped view stays unseeded there.
346
+ */
347
+ merged?: boolean;
342
348
  }): FirstGridPageArgs | null;
343
349
  /**
344
350
  * Read what the screen reads (through `client`, e.g. one served from `custom.table_page_bundle`)
@@ -346,6 +352,7 @@ declare function firstGridPageArgs(input: {
346
352
  */
347
353
  declare function firstGridPageRequest(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
348
354
  pageSize?: number;
355
+ merged?: boolean;
349
356
  }): Promise<FirstGridPageArgs | null>;
350
357
  /**
351
358
  * A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON (lane SSR-ROWS).
@@ -389,6 +396,12 @@ declare function askTablePageSeed(input: {
389
396
  /** The table is drawn EMBEDDED (`TablePage presentation="embedded"`, a Space's inline table): its page size. */
390
397
  embedded?: boolean;
391
398
  pageSize?: number;
399
+ /**
400
+ * The host draws the merged grid. A view grouped by one field (no sub-levels, pivot or drill trail) is then
401
+ * seeded too: the page in group order, the group counts, the summary bar, and the words of the links on the
402
+ * page — each asked with the arguments the grid sends, so the browser's own call finds its answer.
403
+ */
404
+ merged?: boolean;
392
405
  }): Promise<RecordsSeed>;
393
406
 
394
407
  export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf };
@@ -1,10 +1,10 @@
1
1
  import { recordingDataSource, asRecordsDataSource, createRecordsClient, mapPgError } from '@ai-matrx/records/core';
2
+ import { isUuidShape } from '@ai-matrx/kit/uuid';
2
3
  import { PARITY_FIELD_TYPES, KERNEL_TABLES, mintOpId, fieldKindFor } from '@ai-matrx/records';
3
4
  import '@ai-matrx/kit/text-case';
4
5
  import '@ai-matrx/design-system/data-table';
5
6
  import { parseTableStyle, tableStyleIsEmpty } from '@ai-matrx/design-system/data-table/table-style';
6
7
  import '@ai-matrx/design-system/field-formats';
7
- import { isUuidShape } from '@ai-matrx/kit/uuid';
8
8
  import { defineChannelNamespace } from '@ai-matrx/realtime';
9
9
 
10
10
  // src/firstPage.ts
@@ -46,6 +46,20 @@ function defaultSortOf(table, fields) {
46
46
  const field = fields.find((f) => f.key === name) ?? fields.find((f) => f.id === name);
47
47
  return field ? { id: field.key, direction } : null;
48
48
  }
49
+ var MEASURE_LABEL = {
50
+ count: "Count",
51
+ sum: "Sum",
52
+ avg: "Average",
53
+ min: "Smallest",
54
+ max: "Largest",
55
+ median: "Median",
56
+ filled: "Filled",
57
+ empty: "Empty",
58
+ unique: "Unique",
59
+ percent_empty: "Percent empty",
60
+ percent_filled: "Percent filled",
61
+ range: "Range"
62
+ };
49
63
  function holdsNumbers(field) {
50
64
  const kind = String(editorKindFor(field));
51
65
  if (kind === "number" || kind === "currency" || kind === "percent") return true;
@@ -63,6 +77,26 @@ function pageSortFor(fields, sort) {
63
77
  if (!field) return [];
64
78
  return [{ field: field.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field) }];
65
79
  }
80
+
81
+ // src/groupReads.ts
82
+ function isPlainGrouping(grouping) {
83
+ return Boolean(grouping) && !(grouping.then && grouping.then.length > 0) && !grouping.across && !(grouping.trail && grouping.trail.length > 0);
84
+ }
85
+ function groupedPageSort(fields, groupKey, sort) {
86
+ if (!groupKey) return sort;
87
+ const field = fields.find((f) => f.key === groupKey);
88
+ if (!field) return sort;
89
+ return [{ field: groupKey, direction: "asc", as: sortAsFor(field) }, ...sort.filter((s) => s.field !== groupKey)];
90
+ }
91
+ function chosenEntries(chosen) {
92
+ return JSON.parse(JSON.stringify(Object.entries(chosen).sort())).filter(([, op]) => op in MEASURE_LABEL);
93
+ }
94
+ function groupFactMeasures(chosen) {
95
+ return [{ op: "count" }, ...chosenEntries(chosen).filter(([, op]) => op !== "count").map(([key, op]) => ({ op, key }))];
96
+ }
97
+ function summaryBarMeasures(chosen) {
98
+ return chosenEntries(chosen).map(([key, op]) => op === "count" ? { op: "count" } : { op, key });
99
+ }
66
100
  var MIN_ROW_HEIGHT = 24;
67
101
  var MAX_ROW_HEIGHT = 96;
68
102
  var AGGREGATE_KINDS = [
@@ -959,7 +993,8 @@ function firstGridPageArgs(input) {
959
993
  if (shown.ruleId) return null;
960
994
  if (shown.order === "manual") return null;
961
995
  if (shown.filters && Object.keys(shown.filters).length > 0) return null;
962
- if (shown.presentation?.grouping) return null;
996
+ const grouping = shown.presentation?.grouping;
997
+ if (grouping && !(input.merged && isPlainGrouping(grouping))) return null;
963
998
  const first = (shown.sorts ?? [])[0];
964
999
  const viewSort = first && fields.some((f) => f.key === first.field) ? { id: first.field, direction: first.direction } : null;
965
1000
  const opening = viewSort ?? defaultSortOf(input.table, fields);
@@ -967,13 +1002,16 @@ function firstGridPageArgs(input) {
967
1002
  p_table_id: input.tableId,
968
1003
  p_filter: {},
969
1004
  p_search: null,
970
- p_sort: pageSortFor(fields, opening),
1005
+ p_sort: groupedPageSort(fields, grouping?.field ?? null, pageSortFor(fields, opening)),
971
1006
  p_view_id: null,
972
1007
  p_limit: input.pageSize ?? TABLE_PAGE_SIZE,
973
1008
  p_offset: 0
974
1009
  };
975
1010
  }
976
1011
  async function firstGridPageRequest(client, tableId, options = {}) {
1012
+ return (await firstGridOpening(client, tableId, options))?.args ?? null;
1013
+ }
1014
+ async function firstGridOpening(client, tableId, options) {
977
1015
  const [table, fields, views, looks] = await Promise.all([
978
1016
  client.tableRead({ table_id: tableId }),
979
1017
  client.fields({ table_id: tableId }),
@@ -984,7 +1022,15 @@ async function firstGridPageRequest(client, tableId, options = {}) {
984
1022
  const mine = views.data.map(viewFromRow).filter((v) => v.subject === tableId);
985
1023
  const view = defaultViewOf(mine) ?? mine[0] ?? null;
986
1024
  const shown = view ? applyLook(view, looks.data[view.id]) : null;
987
- return firstGridPageArgs({ tableId, table: table.data, fields: fields.data, shown, ...options.pageSize ? { pageSize: options.pageSize } : {} });
1025
+ const args = firstGridPageArgs({
1026
+ tableId,
1027
+ table: table.data,
1028
+ fields: fields.data,
1029
+ shown,
1030
+ ...options.pageSize ? { pageSize: options.pageSize } : {},
1031
+ ...options.merged ? { merged: true } : {}
1032
+ });
1033
+ return args ? { args, shown, fields: fields.data } : null;
988
1034
  }
989
1035
  function serverRowsOf(seed) {
990
1036
  const found = seed?.answers.find((a) => a.door === "server_rows");
@@ -1006,10 +1052,11 @@ async function askTablePageSeed(input) {
1006
1052
  ).catch(() => null) : null;
1007
1053
  try {
1008
1054
  const pageSize = input.pageSize ?? (input.embedded ? EMBEDDED_PAGE_SIZE : TABLE_PAGE_SIZE);
1009
- const args = await firstGridPageRequest(client, input.tableId, { pageSize });
1055
+ const opening = await firstGridOpening(client, input.tableId, { pageSize, ...input.merged ? { merged: true } : {} });
1010
1056
  const wantRows = typeof input.rows === "function" ? input.rows(recording.seed()) : input.rows !== false;
1011
- if (args && wantRows) {
1012
- await client.listPage({
1057
+ if (opening && wantRows) {
1058
+ const { args, shown, fields } = opening;
1059
+ const page = await client.listPage({
1013
1060
  table_id: args.p_table_id,
1014
1061
  filter: args.p_filter,
1015
1062
  search: args.p_search,
@@ -1018,6 +1065,26 @@ async function askTablePageSeed(input) {
1018
1065
  limit: args.p_limit,
1019
1066
  offset: args.p_offset
1020
1067
  });
1068
+ if (page.ok) {
1069
+ const chosen = shown?.presentation?.summaries ?? {};
1070
+ const groupKey = shown?.presentation?.grouping?.field ?? null;
1071
+ const reads = [];
1072
+ if (groupKey) {
1073
+ reads.push(client.recordAggregate({ table_id: input.tableId, groupBy: [groupKey], measures: groupFactMeasures(chosen), limit: 1e3 }));
1074
+ }
1075
+ const bar = summaryBarMeasures(chosen);
1076
+ if (bar.length > 0) reads.push(client.recordAggregate({ table_id: input.tableId, measures: bar }));
1077
+ for (const field of fields) {
1078
+ if (field.type !== "list" && field.type !== "relation") continue;
1079
+ const ids = /* @__PURE__ */ new Set();
1080
+ for (const row of page.data.rows) {
1081
+ const value = row.document?.[field.key];
1082
+ for (const id of Array.isArray(value) ? value.map(String) : typeof value === "string" ? [value] : []) if (isUuidShape(id)) ids.add(id);
1083
+ }
1084
+ if (ids.size > 0) reads.push(client.relationWordsMany({ field_id: field.id, record_ids: [...ids] }));
1085
+ }
1086
+ await Promise.all(reads.map((read) => read.catch(() => null)));
1087
+ }
1021
1088
  }
1022
1089
  } catch {
1023
1090
  }