@platforma-sdk/ui-vue 1.63.12 → 1.65.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.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +38 -32
  2. package/.turbo/turbo-formatter$colon$check.log +2 -2
  3. package/.turbo/turbo-linter$colon$check.log +2 -2
  4. package/.turbo/turbo-types$colon$check.log +1 -1
  5. package/CHANGELOG.md +24 -0
  6. package/dist/components/PlAdvancedFilter/FilterEditor.js.map +1 -1
  7. package/dist/components/PlAdvancedFilter/FilterEditor.style.js.map +1 -1
  8. package/dist/components/PlAdvancedFilter/FilterEditor.vue.d.ts +3 -8
  9. package/dist/components/PlAdvancedFilter/FilterEditor.vue.d.ts.map +1 -1
  10. package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js +164 -151
  11. package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js.map +1 -1
  12. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.js.map +1 -1
  13. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.style.js +8 -7
  14. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.style.js.map +1 -1
  15. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.css +1 -1
  16. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts +24 -8
  17. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts.map +1 -1
  18. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js +176 -110
  19. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js.map +1 -1
  20. package/dist/components/PlAdvancedFilter/types.d.ts +2 -0
  21. package/dist/components/PlAdvancedFilter/types.d.ts.map +1 -1
  22. package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
  23. package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
  24. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
  25. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +116 -109
  26. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
  27. package/dist/components/PlAgDataTable/compositions/useFilterableColumns.js +3 -3
  28. package/dist/components/PlAgDataTable/compositions/useFilterableColumns.js.map +1 -1
  29. package/dist/components/PlAgDataTable/sources/table-source-v2.d.ts +6 -5
  30. package/dist/components/PlAgDataTable/sources/table-source-v2.d.ts.map +1 -1
  31. package/dist/components/PlAgDataTable/sources/table-source-v2.js +122 -88
  32. package/dist/components/PlAgDataTable/sources/table-source-v2.js.map +1 -1
  33. package/dist/components/PlAgDataTable/sources/table-state-v2.d.ts +6 -3
  34. package/dist/components/PlAgDataTable/sources/table-state-v2.d.ts.map +1 -1
  35. package/dist/components/PlAgDataTable/sources/table-state-v2.js +182 -97
  36. package/dist/components/PlAgDataTable/sources/table-state-v2.js.map +1 -1
  37. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.js.map +1 -1
  38. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.style.js.map +1 -1
  39. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue.d.ts.map +1 -1
  40. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue2.js +37 -42
  41. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue2.js.map +1 -1
  42. package/dist/components/PlAgGridColumnManager/useFilteredItems.d.ts +5 -5
  43. package/dist/components/PlAgGridColumnManager/useFilteredItems.d.ts.map +1 -1
  44. package/dist/components/PlAgGridColumnManager/useFilteredItems.js +2 -2
  45. package/dist/components/PlAgGridColumnManager/useFilteredItems.js.map +1 -1
  46. package/dist/components/PlAgGridColumnManager/useGridColumns.js +14 -0
  47. package/dist/components/PlAgGridColumnManager/useGridColumns.js.map +1 -0
  48. package/dist/components/PlAnnotations/components/FilterSidebar.js.map +1 -1
  49. package/dist/components/PlAnnotations/components/FilterSidebar.style.js.map +1 -1
  50. package/dist/components/PlAnnotations/components/FilterSidebar.vue.d.ts.map +1 -1
  51. package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js +7 -4
  52. package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js.map +1 -1
  53. package/dist/components/PlTableFilters/PlTableFiltersV2.js.map +1 -1
  54. package/dist/components/PlTableFilters/PlTableFiltersV2.style.js +5 -1
  55. package/dist/components/PlTableFilters/PlTableFiltersV2.style.js.map +1 -1
  56. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.css +1 -1
  57. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.d.ts +7 -9
  58. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.d.ts.map +1 -1
  59. package/dist/components/PlTableFilters/PlTableFiltersV2.vue2.js +72 -47
  60. package/dist/components/PlTableFilters/PlTableFiltersV2.vue2.js.map +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +2 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/lib/util/helpers/dist/functions.js.map +1 -1
  65. package/dist/lib/util/helpers/dist/objects.js +4 -1
  66. package/dist/lib/util/helpers/dist/objects.js.map +1 -1
  67. package/package.json +8 -7
  68. package/src/components/PlAdvancedFilter/FilterEditor.vue +99 -55
  69. package/src/components/PlAdvancedFilter/PlAdvancedFilter.vue +163 -95
  70. package/src/components/PlAdvancedFilter/types.ts +6 -1
  71. package/src/components/PlAgDataTable/PlAgDataTableV2.vue +26 -7
  72. package/src/components/PlAgDataTable/compositions/useFilterableColumns.ts +4 -4
  73. package/src/components/PlAgDataTable/sources/table-source-v2.ts +231 -131
  74. package/src/components/PlAgDataTable/sources/table-state-v2.ts +249 -70
  75. package/src/components/PlAgGridColumnManager/PlAgGridColumnManager.vue +17 -35
  76. package/src/components/PlAgGridColumnManager/useFilteredItems.ts +9 -11
  77. package/src/components/PlAgGridColumnManager/useGridColumns.ts +26 -0
  78. package/src/components/PlAnnotations/components/FilterSidebar.vue +3 -2
  79. package/src/components/PlTableFilters/PlTableFiltersV2.vue +76 -26
  80. package/src/index.ts +4 -0
@@ -1,30 +1,32 @@
1
1
  import type {
2
2
  AxesSpec,
3
3
  PTableColumnId,
4
+ PTableColumnSpecAxis,
4
5
  PTableColumnSpecColumn,
6
+ PTableHandle,
5
7
  PTableValue,
6
8
  } from "@platforma-sdk/model";
7
9
  import {
8
10
  canonicalizeJson,
9
11
  getAxisId,
10
- isColumnOptional,
11
12
  pTableValue,
12
13
  type PFrameDriver,
13
14
  type PlDataTableSheet,
14
15
  type PTableVector,
15
16
  type AxisId,
16
- type PlDataTableModel,
17
17
  type PTableColumnSpec,
18
18
  type PTableKey,
19
19
  type PlTableColumnId,
20
20
  type PlTableColumnIdJson,
21
21
  isLabelColumn as isLabelColumnSpec,
22
+ isLinkerColumn as isLinkerColumnSpec,
22
23
  isColumnHidden,
24
+ isColumnOptional,
23
25
  matchAxisId,
24
26
  readAnnotation,
27
+ readAnnotationJson,
25
28
  Annotation,
26
29
  ValueType,
27
- readAnnotationJson,
28
30
  getPTableColumnId,
29
31
  } from "@platforma-sdk/model";
30
32
  import type {
@@ -47,6 +49,7 @@ import { getColumnRenderingSpec } from "./value-rendering";
47
49
  import type { Ref } from "vue";
48
50
  import { isJsonEqual } from "@milaboratories/helpers";
49
51
  import type { DeferredCircular } from "./focus-row";
52
+ import { isNil, uniq } from "es-toolkit";
50
53
 
51
54
  export function isLabelColumn(column: PTableColumnSpec): column is PTableColumnSpecColumn {
52
55
  return column.type === "column" && isLabelColumnSpec(column.spec);
@@ -56,20 +59,24 @@ export function isLabelColumn(column: PTableColumnSpec): column is PTableColumnS
56
59
  function columns2rows(
57
60
  fields: number[],
58
61
  columns: PTableVector[],
59
- axes: number[],
60
- resultMapping: number[],
62
+ fieldResultMapping: number[],
63
+ axesResultIndices: number[],
61
64
  ): PlAgDataTableV2Row[] {
62
65
  const rowData: PlAgDataTableV2Row[] = [];
63
66
  for (let iRow = 0; iRow < columns[0].data.length; ++iRow) {
64
- const axesKey: PTableKey = axes.map((iAxis) =>
65
- pTableValue(columns[resultMapping[iAxis]], iRow),
66
- );
67
+ const axesKey: PTableKey = axesResultIndices.map((ri) => pTableValue(columns[ri], iRow));
67
68
  const id = canonicalizeJson<PlTableRowId>(axesKey);
68
- const row: PlAgDataTableV2Row = { id, axesKey };
69
- fields.forEach((field, iCol) => {
70
- row[field.toString() as `${number}`] =
71
- resultMapping[iCol] === -1 ? PTableHidden : pTableValue(columns[resultMapping[iCol]], iRow);
72
- });
69
+ const row = fields.reduce<PlAgDataTableV2Row>(
70
+ (acc, field, iCol) => {
71
+ acc[field.toString() as `${number}`] =
72
+ fieldResultMapping[iCol] === -1
73
+ ? PTableHidden
74
+ : pTableValue(columns[fieldResultMapping[iCol]], iRow);
75
+ return acc;
76
+ },
77
+ { id, axesKey },
78
+ );
79
+
73
80
  rowData.push(row);
74
81
  }
75
82
  return rowData;
@@ -79,16 +86,18 @@ function columns2rows(
79
86
  export async function calculateGridOptions({
80
87
  generation,
81
88
  pfDriver,
82
- model,
83
89
  sheets,
90
+ fullTableHandle,
91
+ visibleTableHandle,
84
92
  dataRenderedTracker,
85
93
  hiddenColIds,
86
94
  cellButtonAxisParams,
87
95
  }: {
88
- generation: Ref<number>;
89
- pfDriver: PFrameDriver;
90
- model: PlDataTableModel;
91
96
  sheets: PlDataTableSheet[];
97
+ pfDriver: PFrameDriver;
98
+ generation: Ref<number>;
99
+ fullTableHandle: PTableHandle;
100
+ visibleTableHandle: PTableHandle;
92
101
  dataRenderedTracker: DeferredCircular<GridApi<PlAgDataTableV2Row>>;
93
102
  hiddenColIds?: PlTableColumnIdJson[];
94
103
  cellButtonAxisParams?: PlAgCellButtonAxisParams;
@@ -98,129 +107,60 @@ export async function calculateGridOptions({
98
107
  }
99
108
  > {
100
109
  const stateGeneration = generation.value;
101
- const stateChangedError = new Error("table state generation changed");
102
110
 
103
111
  // get specs of the full table
104
- const specs = await pfDriver.getSpec(model.fullTableHandle);
105
- if (stateGeneration !== generation.value) throw stateChangedError;
112
+ const [tableSpecs, visibleTableSpecs] = await Promise.all([
113
+ pfDriver.getSpec(fullTableHandle),
114
+ pfDriver.getSpec(visibleTableHandle),
115
+ ]);
106
116
 
107
- // get specs of the visible table (with hidden columns omitted)
108
- const pt = model.visibleTableHandle;
109
- const dataSpecs = await pfDriver.getSpec(pt);
110
- if (stateGeneration !== generation.value) throw stateChangedError;
117
+ if (stateGeneration !== generation.value) throw new Error("table state generation changed");
111
118
 
112
- // create index mapping from full specs to visible subset (hidden columns would have -1)
113
- const specId = (spec: PTableColumnSpec) =>
114
- canonicalizeJson<PTableColumnId>(getPTableColumnId(spec));
115
- const dataSpecsMap = new Map(dataSpecs.entries().map(([i, spec]) => [specId(spec), i]));
116
- const specsToDataSpecsMapping = new Map(
117
- specs.entries().map(([i, spec]) => {
118
- const dataSpecIdx = dataSpecsMap.get(specId(spec)) ?? -1;
119
- if (dataSpecIdx === -1 && spec.type === "axis")
120
- throw new Error(`axis ${JSON.stringify(spec.spec)} not present in join result`);
121
- return [i, dataSpecIdx];
122
- }),
123
- );
124
-
125
- // gether indices of columns that would be displayed in the table
119
+ // index mapping from full specs to visible subset (hidden columns -1)
120
+ const specsToVisibleSpecsMapping = buildSpecsToVisibleSpecsMapping(tableSpecs, visibleTableSpecs);
126
121
 
127
- const sheetAxesIds = sheets.map((sheet) => getAxisId(sheet.axis));
128
- const isPartitionedAxis = (axisId: AxisId) => sheetAxesIds.some((id) => matchAxisId(id, axisId));
129
-
130
- const labelColumns: { axisId: AxisId; labelColumnIdx: number }[] = [];
131
- const setLabelColumnIndex = (axisId: AxisId, labelColumnIdx: number) => {
132
- if (!labelColumns.some((info) => matchAxisId(info.axisId, axisId))) {
133
- labelColumns.push({ axisId, labelColumnIdx });
134
- } else {
135
- console.warn(`multiple label columns match axisId: ${JSON.stringify(axisId)}`);
136
- }
137
- };
138
- const getLabelColumnIndex = (axisId: AxisId) => {
139
- return labelColumns.find((info) => matchAxisId(info.axisId, axisId))?.labelColumnIdx ?? -1;
140
- };
122
+ const isPartitionedAxis = createPartitionedAxisPredicate(sheets);
141
123
 
142
- // filter out partitioned axes, label columns and hidden columns
143
- let indices = specs
144
- .entries()
145
- .filter(([i, spec]) => {
146
- switch (spec.type) {
147
- case "axis":
148
- return !isPartitionedAxis(spec.id);
149
- case "column":
150
- if (isLabelColumnSpec(spec.spec)) {
151
- const labeledAxisId = getAxisId(spec.spec.axesSpec[0]);
152
- if (!isPartitionedAxis(labeledAxisId)) {
153
- setLabelColumnIndex(labeledAxisId, i);
154
- }
155
- return false;
156
- }
157
- return !isColumnHidden(spec.spec);
158
- }
159
- })
160
- .map(([i]) => i)
161
- .toArray();
124
+ // label columns indexed by labeled axis (for axis→label replacement later)
125
+ const getLabelColumnIndex = collectLabelColumnsByAxis(tableSpecs, isPartitionedAxis);
162
126
 
163
- // order columns by priority
164
- indices.sort((a, b) => {
165
- if (specs[a].type !== specs[b].type) return specs[a].type === "axis" ? -1 : 1;
127
+ // displayable column indices ordered: axes first, then columns by OrderPriority
128
+ const fields = sortIndicesByTypeAndPriority(
129
+ selectDisplayableIndices(tableSpecs, isPartitionedAxis),
130
+ tableSpecs,
131
+ );
166
132
 
167
- const aPriority = readAnnotationJson(specs[a].spec, Annotation.Table.OrderPriority);
168
- const bPriority = readAnnotationJson(specs[b].spec, Annotation.Table.OrderPriority);
133
+ // same as fields, but each axis replaced by its label column index when available
134
+ const indices = replaceAxesWithLabelColumns(fields, tableSpecs, getLabelColumnIndex);
169
135
 
170
- if (aPriority === undefined) return bPriority === undefined ? 0 : 1;
171
- if (bPriority === undefined) return -1;
172
- return bPriority - aPriority;
173
- });
136
+ // default hidden columns derived from Optional annotation when no saved state
137
+ const resolvedHiddenColIds =
138
+ hiddenColIds ?? computeDefaultHiddenColIds(fields, indices, tableSpecs);
174
139
 
175
- // fields are indices of columns that would go to columnDefs
176
- const fields = [...indices];
177
- // replace axes with label columns
178
- indices = indices.map((i) => {
179
- const spec = specs[i];
180
- if (spec.type === "axis") {
181
- const labelColumnIdx = getLabelColumnIndex(spec.id);
182
- if (labelColumnIdx !== -1) {
183
- return labelColumnIdx;
184
- }
185
- }
186
- return i;
187
- });
188
140
  const columnDefs: ColDef<PlAgDataTableV2Row, PTableValue | PTableHidden>[] = [
189
141
  makeRowNumberColDef(),
190
142
  ...fields.map((field, index) =>
191
- makeColDef(field, specs[field], specs[indices[index]], hiddenColIds, cellButtonAxisParams),
143
+ makeColDef(
144
+ field,
145
+ tableSpecs[field],
146
+ tableSpecs[indices[index]],
147
+ resolvedHiddenColIds,
148
+ cellButtonAxisParams,
149
+ ),
192
150
  ),
193
151
  ];
194
152
 
195
- // mix in indices of skipped axes (axes that were partitioned or replaced with label columns)
196
- const axesSpec = specs
197
- .values()
198
- .filter((spec) => spec.type === "axis")
199
- .map((spec) => spec.spec)
200
- .toArray();
201
- const axes = axesSpec
202
- .keys()
203
- .map((i) => {
204
- let r = indices.indexOf(i);
205
- if (r === -1) {
206
- r = indices.length;
207
- indices.push(i);
208
- }
209
- return r;
210
- })
211
- .toArray();
153
+ // axes taken directly from visible table (always present as part of join)
154
+ const visibleAxes = collectVisibleAxes(visibleTableSpecs);
212
155
 
213
- const requestIndices: number[] = [];
214
- const resultMapping: number[] = [];
215
- indices.forEach((idx) => {
216
- const dataSpecIdx = specsToDataSpecsMapping.get(idx)!;
217
- if (dataSpecIdx !== -1) {
218
- resultMapping.push(requestIndices.length);
219
- requestIndices.push(dataSpecIdx);
220
- } else {
221
- resultMapping.push(-1);
222
- }
223
- });
156
+ // request indices: non-hidden display fields + visible axes for row selection keys.
157
+ // Axes replaced by label columns request label data (display); original axis values
158
+ // are fetched via visibleAxes (row keys).
159
+ const { requestIndices, fieldResultMapping, axesResultIndices } = buildRequestIndices(
160
+ indices,
161
+ specsToVisibleSpecsMapping,
162
+ visibleAxes.map(([i]) => i),
163
+ );
224
164
 
225
165
  let rowCount = -1;
226
166
  let lastParams: IServerSideGetRowsParams | undefined = undefined;
@@ -229,7 +169,7 @@ export async function calculateGridOptions({
229
169
  if (stateGeneration !== generation.value) return params.fail();
230
170
  try {
231
171
  if (rowCount === -1) {
232
- const ptShape = await pfDriver.getShape(pt);
172
+ const ptShape = await pfDriver.getShape(visibleTableHandle);
233
173
  if (stateGeneration !== generation.value || params.api.isDestroyed())
234
174
  return params.fail();
235
175
  rowCount = ptShape.rows;
@@ -259,13 +199,13 @@ export async function calculateGridOptions({
259
199
  ) {
260
200
  length = Math.min(rowCount, params.request.endRow) - params.request.startRow;
261
201
  if (length > 0) {
262
- const data = await pfDriver.getData(pt, requestIndices, {
202
+ const data = await pfDriver.getData(visibleTableHandle, requestIndices, {
263
203
  offset: params.request.startRow,
264
204
  length,
265
205
  });
266
206
  if (stateGeneration !== generation.value || params.api.isDestroyed())
267
207
  return params.fail();
268
- rowData = columns2rows(fields, data, axes, resultMapping);
208
+ rowData = columns2rows(fields, data, fieldResultMapping, axesResultIndices);
269
209
  }
270
210
  }
271
211
 
@@ -287,7 +227,7 @@ export async function calculateGridOptions({
287
227
  };
288
228
 
289
229
  return {
290
- axesSpec,
230
+ axesSpec: visibleAxes.map(([, { spec }]) => spec),
291
231
  columnDefs,
292
232
  serverSideDatasource,
293
233
  };
@@ -324,16 +264,19 @@ export function makeColDef(
324
264
  cellStyle.fontFamily = columnRenderingSpec.fontFamily;
325
265
  }
326
266
  }
267
+ const headerName =
268
+ readAnnotation(labeledSpec.spec, Annotation.Label)?.trim() ??
269
+ readAnnotation(spec.spec, Annotation.Label)?.trim() ??
270
+ `Unlabeled ${spec.type} ${iCol}`;
271
+
327
272
  return {
328
273
  colId,
329
274
  mainMenuItems: defaultMainMenuItems,
330
275
  context: spec,
331
276
  field: `${iCol}`,
332
- headerName:
333
- readAnnotation(labeledSpec.spec, Annotation.Label)?.trim() ??
334
- `Unlabeled ${spec.type} ${iCol}`,
277
+ headerName,
335
278
  lockPosition: spec.type === "axis",
336
- hide: hiddenColIds?.includes(colId) ?? isColumnOptional(spec.spec),
279
+ hide: hiddenColIds !== undefined && hiddenColIds.includes(colId),
337
280
  valueFormatter: columnRenderingSpec.valueFormatter,
338
281
  headerComponent: PlAgColumnHeader,
339
282
  cellRendererSelector: cellButtonAxisParams?.showCellButtonForAxisId
@@ -388,3 +331,160 @@ export function makeColDef(
388
331
  })(),
389
332
  };
390
333
  }
334
+
335
+ type LabelColumnLookup = (axisId: AxisId) => number;
336
+
337
+ /** Build index mapping from full tableSpecs to their position in visibleTableSpecs (missing → -1). */
338
+ function buildSpecsToVisibleSpecsMapping(
339
+ tableSpecs: PTableColumnSpec[],
340
+ visibleTableSpecs: PTableColumnSpec[],
341
+ ): Map<number, number> {
342
+ const specId = (spec: PTableColumnSpec) =>
343
+ canonicalizeJson<PTableColumnId>(getPTableColumnId(spec));
344
+ const visibleSpecsMap = new Map(
345
+ visibleTableSpecs.entries().map(([i, spec]) => [specId(spec), i] as const),
346
+ );
347
+ return new Map(
348
+ tableSpecs.entries().map(([i, spec]) => {
349
+ const visibleSpecIdx = visibleSpecsMap.get(specId(spec));
350
+ return [i, isNil(visibleSpecIdx) ? -1 : visibleSpecIdx];
351
+ }),
352
+ );
353
+ }
354
+
355
+ /** Predicate that returns true when an axis is one of the sheet partition axes. */
356
+ function createPartitionedAxisPredicate(sheets: PlDataTableSheet[]): (axisId: AxisId) => boolean {
357
+ const sheetAxesIds = sheets.map((sheet) => getAxisId(sheet.axis));
358
+ return (axisId) => sheetAxesIds.some((id) => matchAxisId(id, axisId));
359
+ }
360
+
361
+ /**
362
+ * Collect label columns (skipping partitioned axes and duplicates) and return a
363
+ * lookup function that resolves labeled axisId → label column index (or -1).
364
+ */
365
+ function collectLabelColumnsByAxis(
366
+ tableSpecs: PTableColumnSpec[],
367
+ isPartitionedAxis: (axisId: AxisId) => boolean,
368
+ ): LabelColumnLookup {
369
+ const labelColumns: { axisId: AxisId; labelColumnIdx: number }[] = [];
370
+ for (const [i, spec] of tableSpecs.entries()) {
371
+ if (spec.type !== "column" || !isLabelColumnSpec(spec.spec)) continue;
372
+ const labeledAxisId = getAxisId(spec.spec.axesSpec[0]);
373
+ if (isPartitionedAxis(labeledAxisId)) continue;
374
+ if (labelColumns.some((info) => matchAxisId(info.axisId, labeledAxisId))) {
375
+ console.warn(`multiple label columns match axisId: ${JSON.stringify(labeledAxisId)}`);
376
+ continue;
377
+ }
378
+ labelColumns.push({ axisId: labeledAxisId, labelColumnIdx: i });
379
+ }
380
+ return (axisId) =>
381
+ labelColumns.find((info) => matchAxisId(info.axisId, axisId))?.labelColumnIdx ?? -1;
382
+ }
383
+
384
+ /** Indices of columns to display: drop partitioned axes, label/linker columns, hidden columns. */
385
+ function selectDisplayableIndices(
386
+ tableSpecs: PTableColumnSpec[],
387
+ isPartitionedAxis: (axisId: AxisId) => boolean,
388
+ ): number[] {
389
+ return tableSpecs
390
+ .entries()
391
+ .filter(([, spec]) => {
392
+ switch (spec.type) {
393
+ case "axis":
394
+ return !isPartitionedAxis(spec.id);
395
+ case "column":
396
+ return (
397
+ !isLabelColumnSpec(spec.spec) &&
398
+ !isColumnHidden(spec.spec) &&
399
+ !isLinkerColumnSpec(spec.spec)
400
+ );
401
+ }
402
+ })
403
+ .map(([i]) => i)
404
+ .toArray();
405
+ }
406
+
407
+ /** Sort: axes first, then columns by OrderPriority annotation (higher priority = further left). */
408
+ function sortIndicesByTypeAndPriority(indices: number[], tableSpecs: PTableColumnSpec[]): number[] {
409
+ const priorityOf = (i: number): number => {
410
+ const spec = tableSpecs[i];
411
+ return spec.type === "column"
412
+ ? (readAnnotationJson(spec.spec, Annotation.Table.OrderPriority) ?? 0)
413
+ : 0;
414
+ };
415
+ return [...indices].sort((a, b) => {
416
+ if (tableSpecs[a].type !== tableSpecs[b].type) {
417
+ return tableSpecs[a].type === "axis" ? -1 : 1;
418
+ }
419
+ return priorityOf(b) - priorityOf(a);
420
+ });
421
+ }
422
+
423
+ /** For each axis entry substitute the index of its matching label column when one exists. */
424
+ function replaceAxesWithLabelColumns(
425
+ fields: number[],
426
+ tableSpecs: PTableColumnSpec[],
427
+ getLabelColumnIndex: LabelColumnLookup,
428
+ ): number[] {
429
+ return fields.map((i) => {
430
+ const spec = tableSpecs[i];
431
+ const labelIdx = spec.type === "axis" ? getLabelColumnIndex(spec.id) : -1;
432
+ return labelIdx === -1 ? i : labelIdx;
433
+ });
434
+ }
435
+
436
+ /** Default hidden col ids built from columns marked with the Optional annotation. */
437
+ function computeDefaultHiddenColIds(
438
+ fields: number[],
439
+ indices: number[],
440
+ tableSpecs: PTableColumnSpec[],
441
+ ): PlTableColumnIdJson[] {
442
+ return fields.reduce<PlTableColumnIdJson[]>((acc, field, i) => {
443
+ const spec = tableSpecs[field];
444
+ if (spec.type !== "column" || !isColumnOptional(spec.spec)) return acc;
445
+ const labeledSpec = tableSpecs[indices[i]];
446
+ return [...acc, canonicalizeJson<PlTableColumnId>({ source: spec, labeled: labeledSpec })];
447
+ }, []);
448
+ }
449
+
450
+ /** Extract axis indices and specs from the visible table (always present as part of join). */
451
+ function collectVisibleAxes(
452
+ visibleTableSpecs: PTableColumnSpec[],
453
+ ): [number, PTableColumnSpecAxis][] {
454
+ return visibleTableSpecs
455
+ .entries()
456
+ .filter((entry): entry is [number, PTableColumnSpecAxis] => entry[1].type === "axis")
457
+ .toArray();
458
+ }
459
+
460
+ /**
461
+ * Compose request indices for the visible table:
462
+ * non-hidden display fields first, then visible axes (deduplicated).
463
+ * Returns fieldResultMapping (display field → position in requestIndices, -1 if not requested)
464
+ * and axesResultIndices (visible axis → position in requestIndices).
465
+ */
466
+ function buildRequestIndices(
467
+ indices: number[],
468
+ specsToVisibleSpecsMapping: Map<number, number>,
469
+ visibleAxesIndices: number[],
470
+ ): {
471
+ requestIndices: number[];
472
+ fieldResultMapping: number[];
473
+ axesResultIndices: number[];
474
+ } {
475
+ const resolved = indices.map((displayField) => {
476
+ const idx = specsToVisibleSpecsMapping.get(displayField);
477
+ return idx === undefined || idx === -1 ? null : idx;
478
+ });
479
+ const requestedFields = resolved.filter((v): v is number => v !== null);
480
+
481
+ const fieldResultMapping: number[] = [];
482
+ let pos = 0;
483
+ for (const v of resolved) {
484
+ fieldResultMapping.push(v === null ? -1 : pos++);
485
+ }
486
+
487
+ const requestIndices = uniq([...requestedFields, ...visibleAxesIndices]);
488
+ const axesResultIndices = visibleAxesIndices.map((vi) => requestIndices.indexOf(vi));
489
+ return { requestIndices, fieldResultMapping, axesResultIndices };
490
+ }