@platforma-sdk/ui-vue 1.36.0 → 1.37.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 (38) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/lib.css +1 -1
  3. package/dist/lib.js +9163 -9045
  4. package/dist/lib.js.map +1 -1
  5. package/dist/src/AgGridVue/index.d.ts +1 -0
  6. package/dist/src/AgGridVue/index.d.ts.map +1 -1
  7. package/dist/src/AgGridVue/selection.d.ts +30 -0
  8. package/dist/src/AgGridVue/selection.d.ts.map +1 -0
  9. package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts +4 -4
  10. package/dist/src/components/PlAgDataTable/PlAgDataTable.vue.d.ts.map +1 -1
  11. package/dist/src/components/PlAgDataTable/PlAgDataTableSheets.vue.d.ts +31 -0
  12. package/dist/src/components/PlAgDataTable/PlAgDataTableSheets.vue.d.ts.map +1 -0
  13. package/dist/src/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts +9 -14
  14. package/dist/src/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
  15. package/dist/src/components/PlAgDataTable/PlAgRowCount.vue.d.ts.map +1 -1
  16. package/dist/src/components/PlAgDataTable/sources/table-source-v2.d.ts +6 -13
  17. package/dist/src/components/PlAgDataTable/sources/table-source-v2.d.ts.map +1 -1
  18. package/dist/src/components/PlAgDataTable/sources/table-source.d.ts.map +1 -1
  19. package/dist/src/components/PlAgDataTable/sources/table-state-v2.d.ts +8 -0
  20. package/dist/src/components/PlAgDataTable/sources/table-state-v2.d.ts.map +1 -0
  21. package/dist/src/components/PlAgDataTable/types.d.ts +33 -9
  22. package/dist/src/components/PlAgDataTable/types.d.ts.map +1 -1
  23. package/dist/src/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue.d.ts.map +1 -1
  24. package/dist/src/components/PlAgRowNumHeader.vue.d.ts.map +1 -1
  25. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  26. package/package.json +7 -7
  27. package/src/AgGridVue/index.ts +1 -0
  28. package/src/AgGridVue/selection.ts +82 -0
  29. package/src/components/PlAgDataTable/PlAgDataTable.vue +35 -27
  30. package/src/components/PlAgDataTable/PlAgDataTableSheets.vue +125 -0
  31. package/src/components/PlAgDataTable/PlAgDataTableV2.vue +288 -440
  32. package/src/components/PlAgDataTable/PlAgRowCount.vue +5 -4
  33. package/src/components/PlAgDataTable/sources/table-source-v2.ts +33 -57
  34. package/src/components/PlAgDataTable/sources/table-source.ts +1 -2
  35. package/src/components/PlAgDataTable/sources/table-state-v2.ts +160 -0
  36. package/src/components/PlAgDataTable/types.ts +41 -13
  37. package/src/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue +5 -4
  38. package/src/components/PlAgRowNumHeader.vue +24 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.36.0",
3
+ "version": "1.37.0",
4
4
  "type": "module",
5
5
  "main": "dist/lib.js",
6
6
  "module": "dist/lib.js",
@@ -15,14 +15,14 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@milaboratories/miplots4": "^1.0.98",
18
- "ag-grid-enterprise": "^33.0.4",
19
- "ag-grid-vue3": "^33.0.4",
18
+ "ag-grid-enterprise": "^33.3.2",
19
+ "ag-grid-vue3": "^33.3.2",
20
20
  "canonicalize": "~2.1.0",
21
21
  "lru-cache": "^11.1.0",
22
22
  "vue": "^3.5.13",
23
23
  "@milaboratories/biowasm-tools": "^1.1.0",
24
- "@milaboratories/uikit": "^2.2.89",
25
- "@platforma-sdk/model": "^1.34.10"
24
+ "@platforma-sdk/model": "^1.37.0",
25
+ "@milaboratories/uikit": "^2.2.90"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@faker-js/faker": "^9.2.0",
@@ -48,8 +48,8 @@
48
48
  "vue-tsc": "^2.1.10",
49
49
  "yarpm": "^1.2.0",
50
50
  "zod": "~3.23.8",
51
- "@milaboratories/helpers": "^1.6.14",
52
- "@milaboratories/eslint-config": "^1.0.4"
51
+ "@milaboratories/eslint-config": "^1.0.4",
52
+ "@milaboratories/helpers": "^1.6.14"
53
53
  },
54
54
  "scripts": {
55
55
  "test": "vitest run --passWithNoTests",
@@ -1,2 +1,3 @@
1
1
  export * from './useAgGridOptions';
2
2
  export * from './createAgGridColDef';
3
+ export * from './selection';
@@ -0,0 +1,82 @@
1
+ import type { GridApi } from 'ag-grid-enterprise';
2
+
3
+ /**
4
+ * Returns the number of selected rows in the grid.
5
+ * @param gridApi - The grid API.
6
+ * @returns The number of selected rows.
7
+ */
8
+ export function getSelectedRowsCount(gridApi: GridApi) {
9
+ const rowModel = gridApi.getGridOption('rowModelType');
10
+ switch (rowModel) {
11
+ case 'clientSide': {
12
+ return gridApi.getSelectedRows().length;
13
+ }
14
+ case 'serverSide': {
15
+ const state = gridApi.getServerSideSelectionState();
16
+ // `state.selectAll` flag is ignored as we assume `selectAll` was used to select all rows
17
+ return state?.toggledNodes?.length ?? 0;
18
+ }
19
+ }
20
+ return 0;
21
+ }
22
+
23
+ /**
24
+ * Selects all rows in the grid.
25
+ * @param gridApi - The grid API.
26
+ */
27
+ export function selectAll(gridApi: GridApi) {
28
+ const rowModel = gridApi.getGridOption('rowModelType');
29
+ switch (rowModel) {
30
+ case 'clientSide': {
31
+ gridApi.selectAll();
32
+ break;
33
+ }
34
+ case 'serverSide': {
35
+ // Instead of using `selectAll` we set selection state manually
36
+ // as `selectAll` will not give us the selected rows ids.
37
+ // `forEachNode` goes over all cached rows, so increase cached block size
38
+ // to match the number of rows you expect to be selected at most
39
+ gridApi.forEachNode((node) => {
40
+ node.setSelected(true);
41
+ });
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Deselects all rows in the grid.
48
+ * @param gridApi - The grid API.
49
+ */
50
+ export function deselectAll(gridApi: GridApi) {
51
+ const rowModel = gridApi.getGridOption('rowModelType');
52
+ switch (rowModel) {
53
+ case 'clientSide': {
54
+ gridApi.deselectAll();
55
+ break;
56
+ }
57
+ case 'serverSide': {
58
+ gridApi.setServerSideSelectionState({
59
+ selectAll: false,
60
+ toggledNodes: [],
61
+ });
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Returns the total number of rows in the grid.
68
+ * @param gridApi - The grid API.
69
+ * @returns The total number of rows.
70
+ */
71
+ export function getTotalRowsCount(gridApi: GridApi) {
72
+ return gridApi.getDisplayedRowCount();
73
+ }
74
+
75
+ /**
76
+ * Returns true if selection is enabled in the grid.
77
+ * @param gridApi - The grid API.
78
+ * @returns True if selection is enabled.
79
+ */
80
+ export function isSelectionEnabled(gridApi: GridApi) {
81
+ return Boolean(gridApi.getGridOption('rowSelection'));
82
+ }
@@ -10,16 +10,12 @@ import {
10
10
  type ManagedGridOptions,
11
11
  type SortState,
12
12
  type StateUpdatedEvent,
13
- ModuleRegistry,
14
- ClientSideRowModelModule,
15
- ClipboardModule,
16
- CellSelectionModule,
17
- ServerSideRowModelModule,
18
13
  isColumnSelectionCol,
19
14
  } from 'ag-grid-enterprise';
20
15
  import { AgGridVue } from 'ag-grid-vue3';
21
16
  import { PlDropdownLine } from '@milaboratories/uikit';
22
17
  import type {
18
+ CanonicalizedJson,
23
19
  PTableColumnSpecJson,
24
20
  PTableRowKey,
25
21
  } from '@platforma-sdk/model';
@@ -29,12 +25,11 @@ import {
29
25
  type PlDataTableGridStateWithoutSheets,
30
26
  type AxisId,
31
27
  type PlDataTableState,
32
- type PTableColumnSpec,
33
28
  type PTableRecordFilter,
34
29
  type PTableSorting,
35
30
  parseJson,
31
+ canonicalizeJson,
36
32
  } from '@platforma-sdk/model';
37
- import canonicalize from 'canonicalize';
38
33
  import * as lodash from 'lodash';
39
34
  import { computed, nextTick, ref, shallowRef, toRefs, watch } from 'vue';
40
35
  import { AgGridTheme } from '../../lib';
@@ -42,20 +37,13 @@ import PlOverlayLoading from './PlAgOverlayLoading.vue';
42
37
  import PlOverlayNoRows from './PlAgOverlayNoRows.vue';
43
38
  import { updateXsvGridOptions } from './sources/file-source';
44
39
  import { type PlAgCellButtonAxisParams, makeRowId, updatePFrameGridOptions } from './sources/table-source';
45
- import type { PlAgDataTableController, PlDataTableSettings, PlAgDataTableRow, PlDataTableSettingsPTable } from './types';
40
+ import type { PlAgDataTableController, PlDataTableSettings, PlAgDataTableRow, PlDataTableSettingsPTable, PlDataTableColumnsInfo } from './types';
46
41
  import { PlAgGridColumnManager } from '../PlAgGridColumnManager';
47
42
  import { autoSizeRowNumberColumn, PlAgDataTableRowNumberColId } from './sources/row-number';
48
43
  import { focusRow, makeOnceTracker, trackFirstDataRendered } from './sources/focus-row';
49
44
  import PlAgCsvExporter from '../PlAgCsvExporter/PlAgCsvExporter.vue';
50
45
  import { Deferred, isJsonEqual } from '@milaboratories/helpers';
51
46
 
52
- ModuleRegistry.registerModules([
53
- ClientSideRowModelModule,
54
- ClipboardModule,
55
- ServerSideRowModelModule,
56
- CellSelectionModule,
57
- ]);
58
-
59
47
  const tableState = defineModel<PlDataTableState>({ default: { gridState: {} } });
60
48
  const selectedRows = defineModel<PTableRowKey[]>('selectedRows');
61
49
  const props = defineProps<{
@@ -102,7 +90,7 @@ const props = defineProps<{
102
90
  const { settings } = toRefs(props);
103
91
  const emit = defineEmits<{
104
92
  rowDoubleClicked: [key?: PTableRowKey];
105
- columnsChanged: [columns: PTableColumnSpec[]];
93
+ columnsChanged: [info: PlDataTableColumnsInfo];
106
94
  cellButtonClicked: [key?: PTableRowKey];
107
95
  }>();
108
96
 
@@ -154,7 +142,7 @@ const gridState = computed<PlDataTableGridStateWithoutSheets>({
154
142
  },
155
143
  });
156
144
 
157
- const makeSheetId = (axis: AxisId) => canonicalize(getAxisId(axis))!;
145
+ const makeSheetId = (axis: AxisId): CanonicalizedJson<AxisId> => canonicalizeJson(getAxisId(axis))!;
158
146
 
159
147
  function makeFilters(sheetsState: Record<string, string | number>): PTableRecordFilter[] | undefined {
160
148
  if (settings.value?.sourceType !== 'ptable') return undefined;
@@ -173,7 +161,7 @@ function makeFilters(sheetsState: Record<string, string | number>): PTableRecord
173
161
  );
174
162
  }
175
163
 
176
- const sheetsState = computed({
164
+ const sheetsState = computed<Record<CanonicalizedJson<AxisId>, string | number>>({
177
165
  get: () => tableState.value.gridState.sheets ?? {},
178
166
  set: (sheetsState) => {
179
167
  const filters = makeFilters(sheetsState);
@@ -206,7 +194,7 @@ watch(
206
194
  if (oldSettings && oldSettings.sourceType === 'ptable' && lodash.isEqual(settings.sheets, oldSettings.sheets)) return;
207
195
 
208
196
  const oldSheetsState = sheetsState.value;
209
- const newSheetsState: Record<string, string | number> = {};
197
+ const newSheetsState: Record<CanonicalizedJson<AxisId>, string | number> = {};
210
198
  for (const sheet of settings.sheets) {
211
199
  const sheetId = makeSheetId(sheet.axis);
212
200
  newSheetsState[sheetId] = oldSheetsState[sheetId] ?? sheet.defaultValue ?? sheet.options[0]?.value;
@@ -324,9 +312,18 @@ const onGridReady = (event: GridReadyEvent) => {
324
312
  const makePartialState = (state: GridState) => {
325
313
  return {
326
314
  sourceId: gridState.value.sourceId,
327
- columnOrder: state.columnOrder,
328
- sort: state.sort,
329
- columnVisibility: state.columnVisibility as { hiddenColIds: PTableColumnSpecJson[] } | undefined,
315
+ columnOrder: state.columnOrder as {
316
+ orderedColIds: PTableColumnSpecJson[];
317
+ } | undefined,
318
+ sort: state.sort as {
319
+ sortModel: {
320
+ colId: PTableColumnSpecJson;
321
+ sort: 'asc' | 'desc';
322
+ }[];
323
+ } | undefined,
324
+ columnVisibility: state.columnVisibility as {
325
+ hiddenColIds: PTableColumnSpecJson[];
326
+ } | undefined,
330
327
  };
331
328
  };
332
329
 
@@ -367,16 +364,27 @@ watch(
367
364
  if (!oldOptions) return;
368
365
  if (options.rowModelType != oldOptions.rowModelType) ++reloadKey.value;
369
366
  if (options.columnDefs && !lodash.isEqual(options.columnDefs, oldOptions.columnDefs)) {
370
- const isColDef = (def: ColDef | ColGroupDef): def is ColDef => !('children' in def);
371
- const colDefs = options.columnDefs?.filter(isColDef) ?? [];
372
- const columns
367
+ const sourceId = gridState.value.sourceId;
368
+ if (!sourceId) {
369
+ emit('columnsChanged', {
370
+ sourceId: null,
371
+ columns: [],
372
+ });
373
+ } else {
374
+ const isColDef = (def: ColDef | ColGroupDef): def is ColDef => !('children' in def);
375
+ const colDefs = options.columnDefs?.filter(isColDef) ?? [];
376
+ const columns
373
377
  = colDefs
374
378
  .map((def) => def.colId)
375
379
  .filter((colId) => colId !== undefined)
376
380
  .filter((colId) => colId !== PlAgDataTableRowNumberColId)
377
381
  .filter((colId) => !isColumnSelectionCol(colId))
378
382
  .map((colId) => parseJson(colId as PTableColumnSpecJson)) ?? [];
379
- emit('columnsChanged', columns);
383
+ emit('columnsChanged', {
384
+ sourceId,
385
+ columns,
386
+ });
387
+ }
380
388
  }
381
389
  if (!lodash.isEqual(options.loadingOverlayComponentParams, oldOptions.loadingOverlayComponentParams) && options.loading) {
382
390
  gridApi.value?.setGridOption('loading', false);
@@ -386,7 +394,7 @@ watch(
386
394
  { immediate: true },
387
395
  );
388
396
 
389
- const onSheetChanged = (sheetId: string, newValue: string | number) => {
397
+ const onSheetChanged = (sheetId: CanonicalizedJson<AxisId>, newValue: string | number) => {
390
398
  const state = sheetsState.value;
391
399
  if (state[sheetId] === newValue) return;
392
400
  state[sheetId] = newValue;
@@ -0,0 +1,125 @@
1
+ <script lang="ts" setup>
2
+ import {
3
+ PlDropdownLine,
4
+ } from '@milaboratories/uikit';
5
+ import type {
6
+ PlDataTableSheet,
7
+ PlDataTableSheetState,
8
+ } from '@platforma-sdk/model';
9
+ import {
10
+ getAxisId,
11
+ } from '@platforma-sdk/model';
12
+ import {
13
+ computed,
14
+ watchEffect,
15
+ } from 'vue';
16
+ import type {
17
+ PlDataTableSheetsSettings,
18
+ PlDataTableSheetNormalized,
19
+ } from './types';
20
+ import {
21
+ isJsonEqual,
22
+ } from '@milaboratories/helpers';
23
+
24
+ const state = defineModel<PlDataTableSheetState[]>({
25
+ default: [],
26
+ });
27
+ const props = defineProps<{
28
+ settings: Readonly<PlDataTableSheetsSettings>;
29
+ }>();
30
+
31
+ // Normalize sheets: skip sheets with no options, set default values
32
+ const sheets = computed<PlDataTableSheetNormalized[]>(() => {
33
+ return props.settings.sheets
34
+ .filter((sheet) => sheet.options.length > 0)
35
+ .map((sheet, i) => {
36
+ const axisId = getAxisId(sheet.axis);
37
+
38
+ const getDefaultValue = (): string | number => {
39
+ const cachedState = props.settings.cachedState.find((s) => {
40
+ return isJsonEqual(s.axisId, axisId);
41
+ });
42
+ if (cachedState && isValidOption(sheet, cachedState.value)) {
43
+ return cachedState.value;
44
+ }
45
+ if (sheet.defaultValue && isValidOption(sheet, sheet.defaultValue)) {
46
+ return sheet.defaultValue;
47
+ }
48
+ return sheet.options[0].value;
49
+ };
50
+
51
+ const makePrefix = (): string => {
52
+ return (sheet.axis.annotations?.['pl7.app/label']?.trim()
53
+ ?? `Unlabeled axis ${i}`) + ':';
54
+ };
55
+
56
+ return {
57
+ axisId,
58
+ prefix: makePrefix(),
59
+ options: sheet.options,
60
+ defaultValue: getDefaultValue(),
61
+ } satisfies PlDataTableSheetNormalized;
62
+ });
63
+ });
64
+
65
+ function isValidOption(sheet: PlDataTableSheet, value: string | number): boolean {
66
+ return sheet.options.some((option) => option.value === value);
67
+ }
68
+
69
+ // Restore state from settings
70
+ watchEffect(() => {
71
+ const oldState = [...state.value];
72
+ const newState = sheets.value.map((sheet, i) => makeStateEntry(i, sheet.defaultValue));
73
+ if (!isJsonEqual(oldState, newState)) {
74
+ state.value = newState;
75
+ }
76
+ });
77
+
78
+ function makeStateEntry(i: number, value: string | number): PlDataTableSheetState {
79
+ const axisId = sheets.value[i].axisId;
80
+ return {
81
+ axisId,
82
+ value,
83
+ };
84
+ }
85
+
86
+ function onSheetChanged(i: number, newValue: string | number): void {
87
+ const oldState = [...state.value];
88
+ const stateEntry = makeStateEntry(i, newValue);
89
+ if (!isJsonEqual(oldState[i], stateEntry)) {
90
+ const newState = [...oldState];
91
+ newState[i] = stateEntry;
92
+ state.value = newState;
93
+ }
94
+ }
95
+ </script>
96
+
97
+ <template>
98
+ <div
99
+ v-if="$slots['before'] || sheets.length > 0 || $slots['after']"
100
+ :class="$style.container"
101
+ >
102
+ <slot name="before" />
103
+ <template v-for="(sheet, i) in sheets" :key="i">
104
+ <!-- For some reason state[i] is undefined when the sheet initially loads, so v-if to suppress the error -->
105
+ <PlDropdownLine
106
+ v-if="state[i]"
107
+ :model-value="state[i].value"
108
+ :options="sheet.options"
109
+ :prefix="sheet.prefix"
110
+ @update:model-value="(newValue: string | number) => onSheetChanged(i, newValue)"
111
+ />
112
+ </template>
113
+ <slot name="after" />
114
+ </div>
115
+ </template>
116
+
117
+ <style lang="css" module>
118
+ .container {
119
+ display: flex;
120
+ flex-direction: row;
121
+ gap: 12px;
122
+ flex-wrap: wrap;
123
+ z-index: 3;
124
+ }
125
+ </style>