@platforma-sdk/ui-vue 1.80.8 → 1.80.10

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 (26) hide show
  1. package/.turbo/turbo-build.log +14 -14
  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 +23 -0
  6. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.js.map +1 -1
  7. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue.d.ts +2 -1
  8. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue.d.ts.map +1 -1
  9. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue2.js +6 -2
  10. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue2.js.map +1 -1
  11. package/dist/components/PlAgCsvExporter/export-csv.d.ts +16 -5
  12. package/dist/components/PlAgCsvExporter/export-csv.d.ts.map +1 -1
  13. package/dist/components/PlAgCsvExporter/export-csv.js +48 -39
  14. package/dist/components/PlAgCsvExporter/export-csv.js.map +1 -1
  15. package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
  16. package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
  17. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
  18. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +22 -17
  19. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
  20. package/dist/components/PlAgDataTable/sources/table-source-v2.js +1 -1
  21. package/dist/index.js +8 -8
  22. package/dist/lib.js +4 -4
  23. package/package.json +8 -8
  24. package/src/components/PlAgCsvExporter/PlAgCsvExporter.vue +3 -2
  25. package/src/components/PlAgCsvExporter/export-csv.ts +40 -9
  26. package/src/components/PlAgDataTable/PlAgDataTableV2.vue +4 -3
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.80.8 build /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.80.10 build /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder build --target browser-lib
5
5
 
6
6
  Building browser-lib project...
@@ -16,7 +16,7 @@ Building browser-lib project...
16
16
  rendering chunks...
17
17
 
18
18
  [vite:dts] Start generate declaration files...
19
- [vite:dts] Declaration files built in 4782ms.
19
+ [vite:dts] Declaration files built in 4868ms.
20
20
 
21
21
  computing gzip size...
22
22
  dist/components/PlAnnotations/components/PlAnnotations.vue?vue&type=style&index=0&lang.css 0.04 kB │ gzip: 0.06 kB
@@ -83,7 +83,7 @@ dist/components/PlAgRowNumHeader.js
83
83
  dist/components/PlAgCellFile/PlAgCellFile.js 0.21 kB │ gzip: 0.16 kB │ map: 2.49 kB
84
84
  dist/components/PlAgDataTable/PlAgRowCount.js 0.21 kB │ gzip: 0.17 kB │ map: 1.72 kB
85
85
  dist/plugins/Monetization/EndOfPeriod.vue_vue_type_style_index_0_lang.module.js 0.22 kB │ gzip: 0.18 kB │ map: 0.42 kB
86
- dist/components/PlAgCsvExporter/PlAgCsvExporter.js 0.22 kB │ gzip: 0.16 kB │ map: 1.49 kB
86
+ dist/components/PlAgCsvExporter/PlAgCsvExporter.js 0.22 kB │ gzip: 0.16 kB │ map: 1.59 kB
87
87
  dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_style_index_0_lang.module.js 0.22 kB │ gzip: 0.18 kB │ map: 0.42 kB
88
88
  dist/components/PlAgCellProgress/PlAgCellProgress.js 0.23 kB │ gzip: 0.16 kB │ map: 0.55 kB
89
89
  dist/components/PlAgColumnHeader/PlAgColumnHeader.js 0.23 kB │ gzip: 0.17 kB │ map: 3.22 kB
@@ -121,7 +121,7 @@ dist/components/PlAdvancedFilter/FilterEditor.js
121
121
  dist/components/PlAdvancedFilter/OperandButton.js 0.35 kB │ gzip: 0.24 kB │ map: 1.31 kB
122
122
  dist/components/PlAnnotations/components/FilterSidebar.js 0.35 kB │ gzip: 0.24 kB │ map: 5.40 kB
123
123
  dist/components/PlAnnotations/components/PlAnnotations.js 0.35 kB │ gzip: 0.24 kB │ map: 3.11 kB
124
- dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map: 22.33 kB
124
+ dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map: 22.36 kB
125
125
  dist/plugins/Monetization/UserCabinetCard.js 0.35 kB │ gzip: 0.24 kB │ map: 3.00 kB
126
126
  dist/components/PlAdvancedFilter/PlAdvancedFilter.js 0.36 kB │ gzip: 0.24 kB │ map: 13.36 kB
127
127
  dist/components/PlAgDataTable/PlAgOverlayNoRows.js 0.36 kB │ gzip: 0.21 kB │ map: 0.74 kB
@@ -176,7 +176,6 @@ dist/components/PlAgGridColumnManager/useFilteredItems.js
176
176
  dist/lib/util/helpers/dist/objects.js 1.20 kB │ gzip: 0.58 kB │ map: 9.83 kB
177
177
  dist/createModel.js 1.22 kB │ gzip: 0.61 kB │ map: 3.11 kB
178
178
  dist/utils.js 1.26 kB │ gzip: 0.62 kB │ map: 3.40 kB
179
- dist/components/PlAgCsvExporter/PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js 1.28 kB │ gzip: 0.72 kB │ map: 2.05 kB
180
179
  dist/lib/util/helpers/dist/prettyBytes.js 1.29 kB │ gzip: 0.67 kB │ map: 3.79 kB
181
180
  dist/AgGridVue/AgGridTheme.js 1.30 kB │ gzip: 0.59 kB │ map: 2.07 kB
182
181
  dist/components/PlBtnExportArchive/Summary.vue_vue_type_script_setup_true_lang.js 1.31 kB │ gzip: 0.65 kB │ map: 2.35 kB
@@ -184,6 +183,7 @@ dist/components/PlAgDataTable/PlAgRowCount.vue_vue_type_script_setup_true_lang.j
184
183
  dist/internal/UpdateSerializer.js 1.33 kB │ gzip: 0.61 kB │ map: 4.28 kB
185
184
  dist/components/PlAgDataTable/PlAgOverlayLoading.vue_vue_type_script_setup_true_lang.js 1.33 kB │ gzip: 0.76 kB │ map: 2.49 kB
186
185
  dist/components/PlAgDataTable/types.js 1.33 kB │ gzip: 0.53 kB │ map: 7.63 kB
186
+ dist/components/PlAgCsvExporter/PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js 1.34 kB │ gzip: 0.74 kB │ map: 2.16 kB
187
187
  dist/components/PlBtnExportArchive/Item.vue_vue_type_script_setup_true_lang.js 1.36 kB │ gzip: 0.63 kB │ map: 2.58 kB
188
188
  dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue_vue_type_script_setup_true_lang.js 1.46 kB │ gzip: 0.77 kB │ map: 3.56 kB
189
189
  dist/internal/createAppModel.js 1.48 kB │ gzip: 0.75 kB │ map: 3.89 kB
@@ -202,11 +202,11 @@ dist/components/PlAgDataTable/PlAgDataTableSheets.vue_vue_type_script_setup_true
202
202
  dist/components/PlDatasetSelector/PlDatasetSelector.vue_vue_type_script_setup_true_lang.js 2.58 kB │ gzip: 1.07 kB │ map: 6.02 kB
203
203
  dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_script_setup_true_lang.js 2.69 kB │ gzip: 1.12 kB │ map: 4.16 kB
204
204
  dist/lib.js 2.73 kB │ gzip: 0.57 kB │ map: 3.98 kB
205
- dist/components/PlAgCsvExporter/export-csv.js 2.83 kB │ gzip: 1.27 kB │ map: 9.11 kB
206
205
  dist/components/PlAgDataTable/compositions/useGrid.js 2.91 kB │ gzip: 1.29 kB │ map: 6.51 kB
207
206
  dist/components/PlAgColumnHeader/PlAgColumnHeader.vue_vue_type_script_setup_true_lang.js 2.93 kB │ gzip: 1.26 kB │ map: 7.97 kB
208
207
  dist/components/PlAgDataTable/sources/row-number.js 3.01 kB │ gzip: 1.31 kB │ map: 7.47 kB
209
208
  dist/defineApp.js 3.01 kB │ gzip: 1.05 kB │ map: 14.38 kB
209
+ dist/components/PlAgCsvExporter/export-csv.js 3.13 kB │ gzip: 1.39 kB │ map: 10.80 kB
210
210
  dist/plugins/Monetization/LimitCard.vue_vue_type_script_setup_true_lang.js 3.31 kB │ gzip: 1.15 kB │ map: 9.08 kB
211
211
  dist/composition/fileContent.js 3.78 kB │ gzip: 1.45 kB │ map: 12.95 kB
212
212
  dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue_vue_type_script_setup_true_lang.js 3.83 kB │ gzip: 1.64 kB │ map: 6.89 kB
@@ -221,18 +221,18 @@ dist/components/PlBtnExportArchive/PlBtnExportArchive.vue_vue_type_script_setup_
221
221
  dist/internal/createAppV2.js 5.26 kB │ gzip: 1.98 kB │ map: 17.78 kB
222
222
  dist/components/PlAgDataTable/sources/table-state-v2.js 5.66 kB │ gzip: 1.82 kB │ map: 19.14 kB
223
223
  dist/internal/createAppV3.js 6.19 kB │ gzip: 2.34 kB │ map: 21.40 kB
224
- dist/components/PlAgDataTable/sources/table-source-v2.js 7.56 kB │ gzip: 2.88 kB │ map: 27.20 kB
224
+ dist/components/PlAgDataTable/sources/table-source-v2.js 7.59 kB │ gzip: 2.88 kB │ map: 27.20 kB
225
225
  dist/components/PlAdvancedFilter/PlAdvancedFilter.vue_vue_type_script_setup_true_lang.js 8.54 kB │ gzip: 2.59 kB │ map: 18.44 kB
226
226
  dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lang.js 10.58 kB │ gzip: 3.14 kB │ map: 24.73 kB
227
- dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.48 kB │ gzip: 3.95 kB │ map: 29.92 kB
227
+ dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.54 kB │ gzip: 3.96 kB │ map: 29.97 kB
228
228
 
229
229
  [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:
230
- - vite:dts (29%)
231
- - sourcemaps (21%)
232
- - vite:vue (16%)
233
- - vite:css-post (10%)
234
- - vite:css (8%)
230
+ - sourcemaps (30%)
231
+ - vite:dts (25%)
232
+ - vite:vue (12%)
233
+ - vite:css (9%)
234
+ - vite:css-post (8%)
235
235
  See https://rolldown.rs/options/checks#plugintimings for more details.
236
236
  
237
- ✓ built in 5.71s
237
+ ✓ built in 5.75s
238
238
  Build completed successfully
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.80.8 formatter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.80.10 formatter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder formatter --check
5
5
 
6
6
  Checking formatting...
@@ -8,5 +8,5 @@ Checking formatting...
8
8
  Checking formatting...
9
9
 
10
10
  All matched files use the correct format.
11
- Finished in 2658ms on 137 files using 16 threads.
11
+ Finished in 2624ms on 137 files using 16 threads.
12
12
  Format check completed successfully
@@ -1,10 +1,10 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.80.8 linter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.80.10 linter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder linter --check
5
5
 
6
6
  Linting project...
7
7
  ↳ oxlint --config /home/runner/_work/platforma/platforma/sdk/ui-vue/.oxlintrc.json --deny-warnings
8
8
  Found 0 warnings and 0 errors.
9
- Finished in 32ms on 120 files with 108 rules using 16 threads.
9
+ Finished in 28ms on 120 files with 108 rules using 16 threads.
10
10
  Linting completed successfully
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.80.8 types:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.80.10 types:check /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder type-check --target browser-lib
5
5
 
6
6
  ↳ vue-tsc.js --noEmit --project ./tsconfig.json --customConditions ,
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @platforma-sdk/ui-vue
2
2
 
3
+ ## 1.80.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 4ac0df5: Native table export now uses the disambiguated column labels shown in the table (from the `PlDataTable` `columnsMeta` sidecar) instead of the raw spec `pl7.app/label`. This fixes export failures when exporting a table with many columns whose intrinsic labels collide — the native `exportPTable` path rejects duplicate header names.
8
+
9
+ `exportPTable`/`writePTableToFs` options gain an optional `headerNames` field (aligned 1:1 with `columnIndices`). It is additive and backward compatible: older runtimes ignore it and keep deriving header names from the column spec.
10
+
11
+ - Updated dependencies [4ac0df5]
12
+ - @milaboratories/pl-model-common@1.47.3
13
+ - @milaboratories/columns-collection-driver@0.2.3
14
+ - @milaboratories/pf-spec-driver@1.4.24
15
+ - @platforma-sdk/model@1.80.10
16
+ - @milaboratories/uikit@2.15.18
17
+
18
+ ## 1.80.9
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [db6bbfa]
23
+ - @milaboratories/pf-spec-driver@1.4.23
24
+ - @platforma-sdk/model@1.80.8
25
+
3
26
  ## 1.80.8
4
27
 
5
28
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"PlAgCsvExporter.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"PlAgCsvExporter.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle, PlDataTableColumnsMeta } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n columnsMeta?: PlDataTableColumnsMeta;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle, columnsMeta: props.columnsMeta }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":""}
@@ -1,8 +1,9 @@
1
1
  import { GridApi } from 'ag-grid-enterprise';
2
- import { PTableHandle } from '@platforma-sdk/model';
2
+ import { PTableHandle, PlDataTableColumnsMeta } from '@platforma-sdk/model';
3
3
  type __VLS_Props = {
4
4
  api: GridApi;
5
5
  tableHandle?: PTableHandle;
6
+ columnsMeta?: PlDataTableColumnsMeta;
6
7
  };
7
8
  declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
8
9
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"PlAgCsvExporter.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAOzD,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B,CAAC;;AA4FF,wBAMG"}
1
+ {"version":3,"file":"PlAgCsvExporter.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAOjF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC,CAAC;;AA4FF,wBAMG"}
@@ -7,11 +7,15 @@ var _ = /* @__PURE__ */ s({
7
7
  __name: "PlAgCsvExporter",
8
8
  props: {
9
9
  api: {},
10
- tableHandle: {}
10
+ tableHandle: {},
11
+ columnsMeta: {}
11
12
  },
12
13
  setup(s) {
13
14
  let _ = s, { api: v } = u(_), y = t(), b = l(!1), x = () => {
14
- let t = m(_.tableHandle) ? void 0 : { tableHandle: _.tableHandle };
15
+ let t = m(_.tableHandle) ? void 0 : {
16
+ tableHandle: _.tableHandle,
17
+ columnsMeta: _.columnsMeta
18
+ };
15
19
  m(t) || (b.value = !0, e(v.value, t).finally(() => b.value = !1));
16
20
  }, S = g("PlAgCsvExporter");
17
21
  return (e, t) => d(S) && d(y) ? (c(), r(n, {
@@ -1 +1 @@
1
- {"version":3,"file":"PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":";;;;;;;;;;;;EASA,IAAM,IAAQ,GAIR,EAAE,KAAK,MAAY,EAAO,EAAM,EAChC,IAAqB,GAAsB,EAE3C,IAAY,EAAW,GAAM,EAC7B,UAAuB;GAC3B,IAAM,IAA4C,EAAM,EAAM,YAAW,GAErE,KAAA,IADA,EAAE,aAAa,EAAM,aAAY;AAGjC,KAAM,EAAc,KAIxB,EAAU,QAAQ,IAClB,EAAU,EAAQ,OAAO,EAAc,CAAC,cAAe,EAAU,QAAQ,GAAO;KAG5E,IAAiB,EAAkC,kBAAkB;mBAIzD,EAAA,EAAc,IAAI,EAAA,EAAkB,IAAA,GAAA,EAApD,EAIW,GAAA;;GAJ4C,IAAI,EAAA,EAAc;MACvE,EAEa,EAAA,EAAA,EAAA;GAFA,SAAS,EAAA;GAAW,MAAK;GAAU,SAAK,EAAO,GAAc,CAAA,OAAA,CAAA;;oBAE1E,CAAA,GAAA,AAAA,EAAA,OAAA,CAAA,EAF4E,YAE5E,GAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"PlAgCsvExporter.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/PlAgCsvExporter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GridApi } from \"ag-grid-enterprise\";\nimport type { PTableHandle, PlDataTableColumnsMeta } from \"@platforma-sdk/model\";\nimport { PlBtnGhost, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport { shallowRef, toRefs } from \"vue\";\nimport { isNil } from \"es-toolkit\";\nimport { exportCsv, isCsvExportAvailable } from \"./export-csv\";\nimport type { ExportOptions } from \"./export-csv\";\n\nconst props = defineProps<{\n api: GridApi;\n tableHandle?: PTableHandle;\n columnsMeta?: PlDataTableColumnsMeta;\n}>();\nconst { api: gridApi } = toRefs(props);\nconst csvExportAvailable = isCsvExportAvailable();\n\nconst exporting = shallowRef(false);\nconst initiateExport = () => {\n const nativeOptions: ExportOptions | undefined = !isNil(props.tableHandle)\n ? { tableHandle: props.tableHandle, columnsMeta: props.columnsMeta }\n : undefined;\n\n if (isNil(nativeOptions)) {\n return;\n }\n\n exporting.value = true;\n exportCsv(gridApi.value, nativeOptions).finally(() => (exporting.value = false));\n};\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlAgCsvExporter\");\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget && csvExportAvailable\" :to=\"teleportTarget\">\n <PlBtnGhost :loading=\"exporting\" icon=\"export\" @click.stop=\"initiateExport\">\n Export\n </PlBtnGhost>\n </Teleport>\n</template>\n"],"mappings":";;;;;;;;;;;;;EASA,IAAM,IAAQ,GAKR,EAAE,KAAK,MAAY,EAAO,EAAM,EAChC,IAAqB,GAAsB,EAE3C,IAAY,EAAW,GAAM,EAC7B,UAAuB;GAC3B,IAAM,IAA4C,EAAM,EAAM,YAAW,GAErE,KAAA,IADA;IAAE,aAAa,EAAM;IAAa,aAAa,EAAM;IAAY;AAGjE,KAAM,EAAc,KAIxB,EAAU,QAAQ,IAClB,EAAU,EAAQ,OAAO,EAAc,CAAC,cAAe,EAAU,QAAQ,GAAO;KAG5E,IAAiB,EAAkC,kBAAkB;mBAIzD,EAAA,EAAc,IAAI,EAAA,EAAkB,IAAA,GAAA,EAApD,EAIW,GAAA;;GAJ4C,IAAI,EAAA,EAAc;MACvE,EAEa,EAAA,EAAA,EAAA;GAFA,SAAS,EAAA;GAAW,MAAK;GAAU,SAAK,EAAO,GAAc,CAAA,OAAA,CAAA;;oBAE1E,CAAA,GAAA,AAAA,EAAA,OAAA,CAAA,EAF4E,YAE5E,GAAA,CAAA,CAAA,CAAA"}
@@ -1,10 +1,16 @@
1
1
  import { GridApi } from 'ag-grid-enterprise';
2
- import { PTableHandle, PTableColumnSpec, PFrameSpecDriver, WritePTableToFsResult } from '@platforma-sdk/model';
2
+ import { PTableHandle, PTableColumnSpec, PFrameSpecDriver, PlDataTableColumnsMeta, WritePTableToFsResult } from '@platforma-sdk/model';
3
3
  import { Nil } from '@milaboratories/helpers';
4
4
  /** Options for the native table export. */
5
5
  export interface ExportOptions {
6
6
  tableHandle: PTableHandle;
7
7
  defaultFileName?: string;
8
+ /**
9
+ * Sidecar display metadata for the visible table. Supplies the disambiguated
10
+ * header labels the UI shows — the ones the spec's intrinsic `pl7.app/label`
11
+ * may not carry — so the export reproduces exactly what the user sees.
12
+ */
13
+ columnsMeta?: PlDataTableColumnsMeta;
8
14
  }
9
15
  /**
10
16
  * Table export via the platforma desktop runtime. Prompts for a save
@@ -28,10 +34,15 @@ export declare function exportCsv(gridApi: GridApi, nativeOptions: ExportOptions
28
34
  */
29
35
  export declare function isCsvExportAvailable(): boolean;
30
36
  /**
31
- * Collect unified column indices for visible (non-hidden) columns from the
32
- * ag-grid column defs, remapped onto the provided PTable spec array so the
33
- * indices match the current table handle (ColDef.field indices may be stale
37
+ * Collect `[unified index, header name]` pairs for visible (non-hidden) columns
38
+ * from the ag-grid column defs, remapped onto the provided PTable spec array so
39
+ * the indices match the current table handle (ColDef.field indices may be stale
34
40
  * or diverge from the spec order).
41
+ *
42
+ * The header name is the disambiguated label the UI shows — sidecar override
43
+ * (`columnsMeta`) wins over the spec's intrinsic `pl7.app/label`, falling back
44
+ * to the spec name — so the export reproduces exactly the visible headers and
45
+ * avoids the duplicate-label collisions that break the native export path.
35
46
  */
36
- export declare function collectVisibleColumnIndices(gridApi: GridApi, specs: PTableColumnSpec[], pframeSpec: PFrameSpecDriver): Nil | number[];
47
+ export declare function collectVisibleColumns(gridApi: GridApi, specs: PTableColumnSpec[], pframeSpec: PFrameSpecDriver, columnsMeta: PlDataTableColumnsMeta | undefined): Nil | [number, string][];
37
48
  //# sourceMappingURL=export-csv.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"export-csv.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAEV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAI9C,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,YAAY,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,SAAS,GAAG,qBAAqB,CAAC,CA0D5C;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAO9C;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,gBAAgB,EAAE,EACzB,UAAU,EAAE,gBAAgB,GAC3B,GAAG,GAAG,MAAM,EAAE,CAkBhB"}
1
+ {"version":3,"file":"export-csv.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAEV,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAK9C,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,YAAY,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,SAAS,GAAG,qBAAqB,CAAC,CAqE5C;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAO9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,gBAAgB,EAAE,EACzB,UAAU,EAAE,gBAAgB,EAC5B,WAAW,EAAE,sBAAsB,GAAG,SAAS,GAC9C,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAwB1B"}
@@ -1,10 +1,11 @@
1
1
  import { getServices as e } from "../../internal/getServices.js";
2
2
  import "../PlAgDataTable/sources/row-number.js";
3
3
  import "../PlAgDataTable/index.js";
4
- import { XLSX_MAX_ROWS_PER_SHEET as t, getPTableColumnId as n } from "@platforma-sdk/model";
5
- import { isNil as r } from "es-toolkit";
4
+ import { effectiveLabel as t } from "../PlAgDataTable/sources/table-source-v2.js";
5
+ import { XLSX_MAX_ROWS_PER_SHEET as n, getPTableColumnId as r } from "@platforma-sdk/model";
6
+ import { isNil as i } from "es-toolkit";
6
7
  //#region src/components/PlAgCsvExporter/export-csv.ts
7
- var i = [
8
+ var a = [
8
9
  {
9
10
  name: "CSV",
10
11
  extensions: ["csv"]
@@ -21,7 +22,7 @@ var i = [
21
22
  name: "Excel",
22
23
  extensions: ["xlsx"]
23
24
  }
24
- ], a = [
25
+ ], o = [
25
26
  {
26
27
  name: "CSV (gzip)",
27
28
  extensions: ["csv.gz"]
@@ -39,63 +40,71 @@ var i = [
39
40
  extensions: ["tsv"]
40
41
  }
41
42
  ];
42
- function o(e) {
43
+ function s(e) {
43
44
  let t = e.toLowerCase(), n = t.endsWith(".gz") ? t.slice(0, -3) : t;
44
45
  return n.slice(n.lastIndexOf(".") + 1);
45
46
  }
46
- function s(e, t) {
47
+ function c(e, t) {
47
48
  return t.some(({ extensions: t }) => t.some((t) => e.endsWith("." + t)));
48
49
  }
49
- async function c(n, c) {
50
- let { dialog: u, pframe: f, pframeSpec: p } = e();
51
- if (r(u)) throw Error("dialog service is not available in the current environment");
52
- if (r(f)) throw Error("pframe service is not available");
53
- if (r(p)) throw Error("pframeSpec service is not available");
54
- let m = d(n, await f.getSpec(c.tableHandle), p);
55
- if (r(m)) return;
56
- let h = c.defaultFileName ?? `table_${l(/* @__PURE__ */ new Date())}`;
57
- if (typeof f.exportPTable == "function") {
58
- let { rows: e } = await f.getShape(c.tableHandle), n = e > t ? i.filter(({ name: e }) => e !== "Excel") : i, { canceled: a, path: o } = await u.showSaveDialog({
59
- defaultFileName: `${h}.csv`,
60
- filters: n
50
+ async function l(t, r) {
51
+ let { dialog: l, pframe: d, pframeSpec: p } = e();
52
+ if (i(l)) throw Error("dialog service is not available in the current environment");
53
+ if (i(d)) throw Error("pframe service is not available");
54
+ if (i(p)) throw Error("pframeSpec service is not available");
55
+ let m = f(t, await d.getSpec(r.tableHandle), p, r.columnsMeta);
56
+ if (i(m)) return;
57
+ let h = m.map(([e]) => e), g = m.map(([, e]) => e), _ = r.defaultFileName ?? `table_${u(/* @__PURE__ */ new Date())}`;
58
+ if (typeof d.exportPTable == "function") {
59
+ let { rows: e } = await d.getShape(r.tableHandle), t = e > n ? a.filter(({ name: e }) => e !== "Excel") : a, { canceled: o, path: s } = await l.showSaveDialog({
60
+ defaultFileName: `${_}.csv`,
61
+ filters: t
61
62
  });
62
- if (a || r(o) || !s(o, n)) return;
63
- await f.exportPTable(c.tableHandle, {
64
- path: o,
65
- columnIndices: m
63
+ if (o || i(s) || !c(s, t)) return;
64
+ await d.exportPTable(r.tableHandle, {
65
+ path: s,
66
+ columnIndices: h,
67
+ headerNames: g
66
68
  });
67
69
  return;
68
70
  }
69
- let { canceled: g, path: _ } = await u.showSaveDialog({
70
- defaultFileName: `${h}.csv.gz`,
71
- filters: a
71
+ let { canceled: v, path: y } = await l.showSaveDialog({
72
+ defaultFileName: `${_}.csv.gz`,
73
+ filters: o
72
74
  });
73
- if (g || r(_) || !s(_, a)) return;
74
- let v = o(_);
75
- if (!(v !== "csv" && v !== "tsv")) return f.writePTableToFs(c.tableHandle, {
76
- path: _,
77
- format: v,
78
- columnIndices: m,
79
- ..._.toLowerCase().endsWith(".gz") && { compression: { type: "gzip" } }
75
+ if (v || i(y) || !c(y, o)) return;
76
+ let b = s(y);
77
+ if (!(b !== "csv" && b !== "tsv")) return d.writePTableToFs(r.tableHandle, {
78
+ path: y,
79
+ format: b,
80
+ columnIndices: h,
81
+ headerNames: g,
82
+ ...y.toLowerCase().endsWith(".gz") && { compression: { type: "gzip" } }
80
83
  });
81
84
  }
82
- function l(e) {
85
+ function u(e) {
83
86
  let t = (e) => String(e).padStart(2, "0");
84
87
  return `${t(e.getDate())}-${t(e.getMonth() + 1)}-${e.getFullYear()}_${t(e.getHours())}-${t(e.getMinutes())}-${t(e.getSeconds())}`;
85
88
  }
86
- function u() {
89
+ function d() {
87
90
  try {
88
91
  let t = e();
89
- return !r(t?.dialog) && !r(t?.pframe);
92
+ return !i(t?.dialog) && !i(t?.pframe);
90
93
  } catch {
91
94
  return !1;
92
95
  }
93
96
  }
94
- function d(e, t, i) {
95
- let a = e.getColumnDefs();
96
- return r(a) ? void 0 : [...new Set(a.flatMap((e) => "children" in e || e.hide === !0 || r(e.colId) || e.colId === "\"##RowNumberColumnId##\"" ? [] : [e.context]).map((e) => i.findTableColumn(t, n(e))))].filter((e) => e !== -1);
97
+ function f(e, n, a, o) {
98
+ let s = e.getColumnDefs();
99
+ if (i(s)) return;
100
+ let c = (e) => a.findTableColumn(n, r(e)), l = (e) => "children" in e || e.hide === !0 || i(e.colId) || e.colId === "\"##RowNumberColumnId##\"" ? [] : [e.context], u = /* @__PURE__ */ new Map();
101
+ for (let e of s.flatMap(l)) {
102
+ let n = c(e);
103
+ n === -1 || u.has(n) || u.set(n, t(e, o)?.trim() ?? e.spec.name);
104
+ }
105
+ return [...u];
97
106
  }
98
107
  //#endregion
99
- export { c as exportCsv, u as isCsvExportAvailable };
108
+ export { l as exportCsv, d as isCsvExportAvailable };
100
109
 
101
110
  //# sourceMappingURL=export-csv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"export-csv.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"sourcesContent":["import { type ColDef, type ColGroupDef, type GridApi } from \"ag-grid-enterprise\";\nimport type {\n FileFilter,\n PTableHandle,\n PTableColumnSpec,\n PFrameSpecDriver,\n WritePTableToFsResult,\n} from \"@platforma-sdk/model\";\nimport { getPTableColumnId, XLSX_MAX_ROWS_PER_SHEET } from \"@platforma-sdk/model\";\nimport { isNil } from \"es-toolkit\";\nimport { Nil } from \"@milaboratories/helpers\";\nimport { getServices } from \"../../internal/getServices\";\nimport { PlAgDataTableRowNumberColId } from \"../PlAgDataTable\";\n\n/** Options for the native table export. */\nexport interface ExportOptions {\n tableHandle: PTableHandle;\n defaultFileName?: string;\n}\n\n/** Save-dialog file-type filters for the native `exportPTable` path. */\nconst NATIVE_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n { name: \"Parquet\", extensions: [\"parquet\"] },\n { name: \"Excel\", extensions: [\"xlsx\"] },\n];\n\n/**\n * Save-dialog file-type filters for the `writePTableToFs` fallback. csv/tsv\n * only, offered both plain and gzip-compressed — a trailing `.gz` in the\n * chosen path selects gzip compression.\n */\nconst FALLBACK_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV (gzip)\", extensions: [\"csv.gz\"] },\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV (gzip)\", extensions: [\"tsv.gz\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n];\n\n/** Table format from a save path, ignoring a trailing `.gz`. e.g. `\"t.csv.gz\"` → `\"csv\"`. */\nfunction tableFormatFromPath(path: string): string {\n const lower = path.toLowerCase();\n const base = lower.endsWith(\".gz\") ? lower.slice(0, -3) : lower;\n return base.slice(base.lastIndexOf(\".\") + 1);\n}\n\n/** True when `path` ends with one of the filters' extensions (e.g. `\".csv\"`). */\nfunction matchesFilterExtension(path: string, filters: FileFilter[]): boolean {\n return filters.some(({ extensions }) => extensions.some((ext) => path.endsWith(\".\" + ext)));\n}\n\n/**\n * Table export via the platforma desktop runtime. Prompts for a save\n * destination via the `Dialog` service — offering the available output formats\n * as file-type filters so the user picks the format — then writes the PTable to\n * the chosen path.\n *\n * Prefers the native `PFrame.exportPTable` when the runtime advertises it: the\n * table handle is the *visible* one, so exporting the whole handle reproduces\n * exactly the visible columns/rows, and the format (`csv`/`tsv`/`parquet`/\n * `xlsx`) is taken from the chosen file extension. Falls back to streaming the\n * visible columns via `PFrame.writePTableToFs` (`csv`/`tsv`, optionally\n * gzip-compressed when the chosen path ends in `.gz`) on runtimes that do not\n * advertise `exportPTable`.\n */\nexport async function exportCsv(\n gridApi: GridApi,\n nativeOptions: ExportOptions,\n): Promise<undefined | WritePTableToFsResult> {\n const { dialog, pframe, pframeSpec } = getServices();\n if (isNil(dialog)) {\n throw new Error(\"dialog service is not available in the current environment\");\n }\n if (isNil(pframe)) {\n throw new Error(\"pframe service is not available\");\n }\n if (isNil(pframeSpec)) {\n throw new Error(\"pframeSpec service is not available\");\n }\n\n const specs = await pframe.getSpec(nativeOptions.tableHandle);\n const columnIndices = collectVisibleColumnIndices(gridApi, specs, pframeSpec);\n if (isNil(columnIndices)) {\n return undefined;\n }\n\n const baseFileName = nativeOptions.defaultFileName ?? `table_${formatTimestamp(new Date())}`;\n\n if (typeof pframe.exportPTable === \"function\") {\n const { rows } = await pframe.getShape(nativeOptions.tableHandle);\n const filters =\n rows > XLSX_MAX_ROWS_PER_SHEET\n ? NATIVE_EXPORT_FILTERS.filter(({ name }) => name !== \"Excel\")\n : NATIVE_EXPORT_FILTERS;\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv`,\n filters,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, filters)) {\n return undefined;\n }\n\n await pframe.exportPTable(nativeOptions.tableHandle, { path, columnIndices });\n return undefined;\n }\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv.gz`,\n filters: FALLBACK_EXPORT_FILTERS,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, FALLBACK_EXPORT_FILTERS)) {\n return undefined;\n }\n\n const format = tableFormatFromPath(path);\n if (format !== \"csv\" && format !== \"tsv\") {\n return undefined;\n }\n\n return pframe.writePTableToFs(nativeOptions.tableHandle, {\n path,\n format,\n columnIndices,\n ...(path.toLowerCase().endsWith(\".gz\") && { compression: { type: \"gzip\" } }),\n });\n}\n\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()}` +\n `_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`\n );\n}\n\n/**\n * Checks whether the native CSV export capability is available in the\n * current platforma runtime environment. Both `Dialog` and `PFrame`\n * services must be present — desktop-app wires them, web/preview do not.\n */\nexport function isCsvExportAvailable(): boolean {\n try {\n const services = getServices();\n return !isNil(services?.dialog) && !isNil(services?.pframe);\n } catch {\n return false;\n }\n}\n\n/**\n * Collect unified column indices for visible (non-hidden) columns from the\n * ag-grid column defs, remapped onto the provided PTable spec array so the\n * indices match the current table handle (ColDef.field indices may be stale\n * or diverge from the spec order).\n */\nexport function collectVisibleColumnIndices(\n gridApi: GridApi,\n specs: PTableColumnSpec[],\n pframeSpec: PFrameSpecDriver,\n): Nil | number[] {\n const columnDefs = gridApi.getColumnDefs();\n if (isNil(columnDefs)) {\n return;\n }\n\n const findIndex = (spec: PTableColumnSpec) =>\n pframeSpec.findTableColumn(specs, getPTableColumnId(spec));\n\n const specsForDef = (def: ColDef | ColGroupDef): PTableColumnSpec[] => {\n if (\"children\" in def) return [];\n if (def.hide === true) return [];\n if (isNil(def.colId)) return [];\n if (def.colId === PlAgDataTableRowNumberColId) return [];\n return [def.context as PTableColumnSpec];\n };\n\n return [...new Set(columnDefs.flatMap(specsForDef).map(findIndex))].filter((idx) => idx !== -1);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"export-csv.js","names":[],"sources":["../../../src/components/PlAgCsvExporter/export-csv.ts"],"sourcesContent":["import { type ColDef, type ColGroupDef, type GridApi } from \"ag-grid-enterprise\";\nimport type {\n FileFilter,\n PTableHandle,\n PTableColumnSpec,\n PFrameSpecDriver,\n PlDataTableColumnsMeta,\n WritePTableToFsResult,\n} from \"@platforma-sdk/model\";\nimport { getPTableColumnId, XLSX_MAX_ROWS_PER_SHEET } from \"@platforma-sdk/model\";\nimport { isNil } from \"es-toolkit\";\nimport { Nil } from \"@milaboratories/helpers\";\nimport { getServices } from \"../../internal/getServices\";\nimport { PlAgDataTableRowNumberColId } from \"../PlAgDataTable\";\nimport { effectiveLabel } from \"../PlAgDataTable/sources/table-source-v2\";\n\n/** Options for the native table export. */\nexport interface ExportOptions {\n tableHandle: PTableHandle;\n defaultFileName?: string;\n /**\n * Sidecar display metadata for the visible table. Supplies the disambiguated\n * header labels the UI shows — the ones the spec's intrinsic `pl7.app/label`\n * may not carry — so the export reproduces exactly what the user sees.\n */\n columnsMeta?: PlDataTableColumnsMeta;\n}\n\n/** Save-dialog file-type filters for the native `exportPTable` path. */\nconst NATIVE_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n { name: \"Parquet\", extensions: [\"parquet\"] },\n { name: \"Excel\", extensions: [\"xlsx\"] },\n];\n\n/**\n * Save-dialog file-type filters for the `writePTableToFs` fallback. csv/tsv\n * only, offered both plain and gzip-compressed — a trailing `.gz` in the\n * chosen path selects gzip compression.\n */\nconst FALLBACK_EXPORT_FILTERS: FileFilter[] = [\n { name: \"CSV (gzip)\", extensions: [\"csv.gz\"] },\n { name: \"CSV\", extensions: [\"csv\"] },\n { name: \"TSV (gzip)\", extensions: [\"tsv.gz\"] },\n { name: \"TSV\", extensions: [\"tsv\"] },\n];\n\n/** Table format from a save path, ignoring a trailing `.gz`. e.g. `\"t.csv.gz\"` → `\"csv\"`. */\nfunction tableFormatFromPath(path: string): string {\n const lower = path.toLowerCase();\n const base = lower.endsWith(\".gz\") ? lower.slice(0, -3) : lower;\n return base.slice(base.lastIndexOf(\".\") + 1);\n}\n\n/** True when `path` ends with one of the filters' extensions (e.g. `\".csv\"`). */\nfunction matchesFilterExtension(path: string, filters: FileFilter[]): boolean {\n return filters.some(({ extensions }) => extensions.some((ext) => path.endsWith(\".\" + ext)));\n}\n\n/**\n * Table export via the platforma desktop runtime. Prompts for a save\n * destination via the `Dialog` service — offering the available output formats\n * as file-type filters so the user picks the format — then writes the PTable to\n * the chosen path.\n *\n * Prefers the native `PFrame.exportPTable` when the runtime advertises it: the\n * table handle is the *visible* one, so exporting the whole handle reproduces\n * exactly the visible columns/rows, and the format (`csv`/`tsv`/`parquet`/\n * `xlsx`) is taken from the chosen file extension. Falls back to streaming the\n * visible columns via `PFrame.writePTableToFs` (`csv`/`tsv`, optionally\n * gzip-compressed when the chosen path ends in `.gz`) on runtimes that do not\n * advertise `exportPTable`.\n */\nexport async function exportCsv(\n gridApi: GridApi,\n nativeOptions: ExportOptions,\n): Promise<undefined | WritePTableToFsResult> {\n const { dialog, pframe, pframeSpec } = getServices();\n if (isNil(dialog)) {\n throw new Error(\"dialog service is not available in the current environment\");\n }\n if (isNil(pframe)) {\n throw new Error(\"pframe service is not available\");\n }\n if (isNil(pframeSpec)) {\n throw new Error(\"pframeSpec service is not available\");\n }\n\n const specs = await pframe.getSpec(nativeOptions.tableHandle);\n const visibleColumns = collectVisibleColumns(\n gridApi,\n specs,\n pframeSpec,\n nativeOptions.columnsMeta,\n );\n if (isNil(visibleColumns)) {\n return undefined;\n }\n // `columnIndices` is the long-standing field every runtime understands;\n // `headerNames` is additive so a newer UI still works against an older\n // desktop-app runtime (which ignores it and derives labels from the spec).\n const columnIndices = visibleColumns.map(([index]) => index);\n const headerNames = visibleColumns.map(([, name]) => name);\n\n const baseFileName = nativeOptions.defaultFileName ?? `table_${formatTimestamp(new Date())}`;\n\n if (typeof pframe.exportPTable === \"function\") {\n const { rows } = await pframe.getShape(nativeOptions.tableHandle);\n const filters =\n rows > XLSX_MAX_ROWS_PER_SHEET\n ? NATIVE_EXPORT_FILTERS.filter(({ name }) => name !== \"Excel\")\n : NATIVE_EXPORT_FILTERS;\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv`,\n filters,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, filters)) {\n return undefined;\n }\n\n await pframe.exportPTable(nativeOptions.tableHandle, { path, columnIndices, headerNames });\n return undefined;\n }\n\n const { canceled, path } = await dialog.showSaveDialog({\n defaultFileName: `${baseFileName}.csv.gz`,\n filters: FALLBACK_EXPORT_FILTERS,\n });\n if (canceled || isNil(path) || !matchesFilterExtension(path, FALLBACK_EXPORT_FILTERS)) {\n return undefined;\n }\n\n const format = tableFormatFromPath(path);\n if (format !== \"csv\" && format !== \"tsv\") {\n return undefined;\n }\n\n return pframe.writePTableToFs(nativeOptions.tableHandle, {\n path,\n format,\n columnIndices,\n headerNames,\n ...(path.toLowerCase().endsWith(\".gz\") && { compression: { type: \"gzip\" } }),\n });\n}\n\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()}` +\n `_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`\n );\n}\n\n/**\n * Checks whether the native CSV export capability is available in the\n * current platforma runtime environment. Both `Dialog` and `PFrame`\n * services must be present — desktop-app wires them, web/preview do not.\n */\nexport function isCsvExportAvailable(): boolean {\n try {\n const services = getServices();\n return !isNil(services?.dialog) && !isNil(services?.pframe);\n } catch {\n return false;\n }\n}\n\n/**\n * Collect `[unified index, header name]` pairs for visible (non-hidden) columns\n * from the ag-grid column defs, remapped onto the provided PTable spec array so\n * the indices match the current table handle (ColDef.field indices may be stale\n * or diverge from the spec order).\n *\n * The header name is the disambiguated label the UI shows — sidecar override\n * (`columnsMeta`) wins over the spec's intrinsic `pl7.app/label`, falling back\n * to the spec name — so the export reproduces exactly the visible headers and\n * avoids the duplicate-label collisions that break the native export path.\n */\nexport function collectVisibleColumns(\n gridApi: GridApi,\n specs: PTableColumnSpec[],\n pframeSpec: PFrameSpecDriver,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): Nil | [number, string][] {\n const columnDefs = gridApi.getColumnDefs();\n if (isNil(columnDefs)) {\n return;\n }\n\n const findIndex = (spec: PTableColumnSpec) =>\n pframeSpec.findTableColumn(specs, getPTableColumnId(spec));\n\n const specsForDef = (def: ColDef | ColGroupDef): PTableColumnSpec[] => {\n if (\"children\" in def) return [];\n if (def.hide === true) return [];\n if (isNil(def.colId)) return [];\n if (def.colId === PlAgDataTableRowNumberColId) return [];\n return [def.context as PTableColumnSpec];\n };\n\n const byIndex = new Map<number, string>();\n for (const spec of columnDefs.flatMap(specsForDef)) {\n const index = findIndex(spec);\n if (index === -1 || byIndex.has(index)) continue;\n byIndex.set(index, effectiveLabel(spec, columnsMeta)?.trim() ?? spec.spec.name);\n }\n return [...byIndex];\n}\n"],"mappings":";;;;;;;AA6BA,IAAM,IAAsC;CAC1C;EAAE,MAAM;EAAO,YAAY,CAAC,MAAM;EAAE;CACpC;EAAE,MAAM;EAAO,YAAY,CAAC,MAAM;EAAE;CACpC;EAAE,MAAM;EAAW,YAAY,CAAC,UAAU;EAAE;CAC5C;EAAE,MAAM;EAAS,YAAY,CAAC,OAAO;EAAE;CACxC,EAOK,IAAwC;CAC5C;EAAE,MAAM;EAAc,YAAY,CAAC,SAAS;EAAE;CAC9C;EAAE,MAAM;EAAO,YAAY,CAAC,MAAM;EAAE;CACpC;EAAE,MAAM;EAAc,YAAY,CAAC,SAAS;EAAE;CAC9C;EAAE,MAAM;EAAO,YAAY,CAAC,MAAM;EAAE;CACrC;AAGD,SAAS,EAAoB,GAAsB;CACjD,IAAM,IAAQ,EAAK,aAAa,EAC1B,IAAO,EAAM,SAAS,MAAM,GAAG,EAAM,MAAM,GAAG,GAAG,GAAG;AAC1D,QAAO,EAAK,MAAM,EAAK,YAAY,IAAI,GAAG,EAAE;;AAI9C,SAAS,EAAuB,GAAc,GAAgC;AAC5E,QAAO,EAAQ,MAAM,EAAE,oBAAiB,EAAW,MAAM,MAAQ,EAAK,SAAS,MAAM,EAAI,CAAC,CAAC;;AAiB7F,eAAsB,EACpB,GACA,GAC4C;CAC5C,IAAM,EAAE,WAAQ,WAAQ,kBAAe,GAAa;AACpD,KAAI,EAAM,EAAO,CACf,OAAU,MAAM,6DAA6D;AAE/E,KAAI,EAAM,EAAO,CACf,OAAU,MAAM,kCAAkC;AAEpD,KAAI,EAAM,EAAW,CACnB,OAAU,MAAM,sCAAsC;CAIxD,IAAM,IAAiB,EACrB,GAFY,MAAM,EAAO,QAAQ,EAAc,YAAY,EAI3D,GACA,EAAc,YACf;AACD,KAAI,EAAM,EAAe,CACvB;CAKF,IAAM,IAAgB,EAAe,KAAK,CAAC,OAAW,EAAM,EACtD,IAAc,EAAe,KAAK,GAAG,OAAU,EAAK,EAEpD,IAAe,EAAc,mBAAmB,SAAS,kBAAgB,IAAI,MAAM,CAAC;AAE1F,KAAI,OAAO,EAAO,gBAAiB,YAAY;EAC7C,IAAM,EAAE,YAAS,MAAM,EAAO,SAAS,EAAc,YAAY,EAC3D,IACJ,IAAO,IACH,EAAsB,QAAQ,EAAE,cAAW,MAAS,QAAQ,GAC5D,GAEA,EAAE,aAAU,YAAS,MAAM,EAAO,eAAe;GACrD,iBAAiB,GAAG,EAAa;GACjC;GACD,CAAC;AACF,MAAI,KAAY,EAAM,EAAK,IAAI,CAAC,EAAuB,GAAM,EAAQ,CACnE;AAGF,QAAM,EAAO,aAAa,EAAc,aAAa;GAAE;GAAM;GAAe;GAAa,CAAC;AAC1F;;CAGF,IAAM,EAAE,aAAU,YAAS,MAAM,EAAO,eAAe;EACrD,iBAAiB,GAAG,EAAa;EACjC,SAAS;EACV,CAAC;AACF,KAAI,KAAY,EAAM,EAAK,IAAI,CAAC,EAAuB,GAAM,EAAwB,CACnF;CAGF,IAAM,IAAS,EAAoB,EAAK;AACpC,aAAW,SAAS,MAAW,OAInC,QAAO,EAAO,gBAAgB,EAAc,aAAa;EACvD;EACA;EACA;EACA;EACA,GAAI,EAAK,aAAa,CAAC,SAAS,MAAM,IAAI,EAAE,aAAa,EAAE,MAAM,QAAQ,EAAE;EAC5E,CAAC;;AAGJ,SAAS,EAAgB,GAAiB;CACxC,IAAM,KAAO,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACrD,QACE,GAAG,EAAI,EAAE,SAAS,CAAC,CAAC,GAAG,EAAI,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,EAAE,aAAa,CAAA,GAC3D,EAAI,EAAE,UAAU,CAAC,CAAC,GAAG,EAAI,EAAE,YAAY,CAAC,CAAC,GAAG,EAAI,EAAE,YAAY,CAAC;;AASvE,SAAgB,IAAgC;AAC9C,KAAI;EACF,IAAM,IAAW,GAAa;AAC9B,SAAO,CAAC,EAAM,GAAU,OAAO,IAAI,CAAC,EAAM,GAAU,OAAO;SACrD;AACN,SAAO;;;AAeX,SAAgB,EACd,GACA,GACA,GACA,GAC0B;CAC1B,IAAM,IAAa,EAAQ,eAAe;AAC1C,KAAI,EAAM,EAAW,CACnB;CAGF,IAAM,KAAa,MACjB,EAAW,gBAAgB,GAAO,EAAkB,EAAK,CAAC,EAEtD,KAAe,MACf,cAAc,KACd,EAAI,SAAS,MACb,EAAM,EAAI,MAAM,IAChB,EAAI,UAAA,8BAA8C,EAAE,GACjD,CAAC,EAAI,QAA4B,EAGpC,oBAAU,IAAI,KAAqB;AACzC,MAAK,IAAM,KAAQ,EAAW,QAAQ,EAAY,EAAE;EAClD,IAAM,IAAQ,EAAU,EAAK;AACzB,QAAU,MAAM,EAAQ,IAAI,EAAM,IACtC,EAAQ,IAAI,GAAO,EAAe,GAAM,EAAY,EAAE,MAAM,IAAI,EAAK,KAAK,KAAK;;AAEjF,QAAO,CAAC,GAAG,EAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"PlAgDataTableV2.js","names":[],"sources":["../../../src/components/PlAgDataTable/PlAgDataTableV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { promiseTimeout, isJsonEqual } from \"@milaboratories/helpers\";\nimport type {\n AxisId,\n PlDataTableColumnsMeta,\n PlDataTableGridStateCore,\n PlDataTableStateV2,\n PlSelectionModel,\n PlTableColumnIdJson,\n PTableColumnSpec,\n PTableKey,\n} from \"@platforma-sdk/model\";\nimport {\n getRawPlatformaInstance,\n createPlSelectionModel,\n matchAxisId,\n getAxisId,\n canonicalizeJson,\n isAbortError,\n} from \"@platforma-sdk/model\";\nimport type { CellRendererSelectorFunc, GridApi, GridState } from \"ag-grid-enterprise\";\nimport { AgGridVue } from \"ag-grid-vue3\";\nimport { computed, effectScope, nextTick, ref, toRefs, watch, watchEffect } from \"vue\";\nimport PlAgCsvExporter from \"../PlAgCsvExporter/PlAgCsvExporter.vue\";\nimport { PlAgGridColumnManager } from \"../PlAgGridColumnManager\";\nimport PlTableFiltersV2 from \"../PlTableFilters/PlTableFiltersV2.vue\";\nimport { PlTableFastSearch } from \"../PlTableFastSearch\";\nimport PlAgDataTableSheets from \"./PlAgDataTableSheets.vue\";\nimport PlAgRowCount from \"./PlAgRowCount.vue\";\nimport { DeferredCircular, ensureNodeVisible } from \"./sources/focus-row\";\nimport { PlAgDataTableRowNumberColId } from \"./sources/row-number\";\nimport type { PlAgCellButtonAxisParams } from \"./sources/table-source-v2\";\nimport { calculateGridOptions, effectiveVisibility } from \"./sources/table-source-v2\";\nimport { useTableState } from \"./sources/table-state-v2\";\nimport type {\n PlAgDataTableV2Controller,\n PlAgDataTableV2Row,\n PlAgOverlayLoadingParams,\n PlAgOverlayNoRowsParams,\n PlDataTableSettingsV2,\n PlDataTableSheetsSettings,\n PlTableRowId,\n} from \"./types\";\nimport { useFilterableColumns } from \"./compositions/useFilterableColumns\";\nimport { useGrid } from \"./compositions/useGrid\";\nimport { AgGridTheme } from \"../../AgGridVue/AgGridTheme\";\n\nconst tableState = defineModel<PlDataTableStateV2>({\n required: true,\n});\n/** Warning: selection model value updates are ignored, use updateSelection instead */\nconst selection = defineModel<PlSelectionModel>(\"selection\");\nconst props = defineProps<{\n /** Required component settings */\n settings: Readonly<PlDataTableSettingsV2>;\n\n /**\n * The disableColumnsPanel prop controls the display of a button that activates\n * the columns management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableColumnsPanel?: boolean;\n\n /**\n * The disableFiltersPanel prop controls the display of a button that activates\n * the filters management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableFiltersPanel?: boolean;\n\n /**\n * The showExportButton prop controls the display of a button that allows\n * to export table data in CSV format. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n showExportButton?: boolean;\n\n /**\n * The AxisId property is used to configure and display the PlAgTextAndButtonCell component\n */\n showCellButtonForAxisId?: AxisId;\n\n /**\n * If cellButtonInvokeRowsOnDoubleClick = true, clicking a button inside the row\n * triggers the doubleClick event for the entire row.\n *\n * If cellButtonInvokeRowsOnDoubleClick = false, the doubleClick event for the row\n * is not triggered, but will triggered cellButtonClicked event with (key: PTableRowKey) argument.\n */\n cellButtonInvokeRowsOnDoubleClick?: boolean;\n\n /** @see {@link PlAgOverlayLoadingParams.loadingText} */\n loadingText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.runningText} */\n runningText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.notReadyText} */\n notReadyText?: string;\n\n /** @see {@link PlAgOverlayNoRowsParams.text} */\n noRowsText?: string;\n\n /**\n * Callback to override the default renderer for a given cell.\n * @see https://www.ag-grid.com/vue-data-grid/component-cell-renderer/#dynamic-component-selection\n */\n cellRendererSelector?: CellRendererSelectorFunc<PlAgDataTableV2Row>;\n}>();\nconst { settings } = toRefs(props);\nconst emit = defineEmits<{\n rowDoubleClicked: [key?: PTableKey];\n cellButtonClicked: [key?: PTableKey];\n newDataRendered: [];\n}>();\n\nconst dataRenderedTracker = new DeferredCircular<GridApi<PlAgDataTableV2Row>>();\nconst { gridApi, gridOptions } = useGrid({\n selection,\n noRowsText: props.noRowsText,\n runningText: props.runningText,\n loadingText: props.loadingText,\n notReadyText: props.notReadyText,\n cellRendererSelector: props.cellRendererSelector,\n});\nlet isReloading = false;\ngridOptions.value.onGridPreDestroyed = (event) => {\n if (!isReloading) {\n gridOptions.value.initialState = gridState.value = normalizeColumnVisibility(\n makePartialState(event.api.getState()),\n gridState.value,\n event.api,\n currentColumnsMeta(),\n );\n }\n gridApi.value = null;\n};\ngridOptions.value.onRowDoubleClicked = (event) => {\n if (event.data && event.data.axesKey) emit(\"rowDoubleClicked\", event.data.axesKey);\n};\ngridOptions.value.onStateUpdated = (event) => {\n const partialState = normalizeColumnVisibility(\n makePartialState(event.state),\n gridState.value,\n event.api,\n currentColumnsMeta(),\n );\n // We have to keep initialState synchronized with gridState for gridState recovery after key updating.\n gridOptions.value.initialState = gridState.value = partialState;\n\n if (!isJsonEqual(event.sources, [\"columnSizing\"])) {\n event.api.autoSizeColumns(\n event.api\n .getAllDisplayedColumns()\n .filter((column) => column.getColId() !== PlAgDataTableRowNumberColId),\n );\n }\n};\n\nconst [filterableColumns, visibleFilterableColumns] = useFilterableColumns(\n () => settings.value.sourceId,\n () => gridOptions.value.columnDefs ?? null,\n);\nconst defaultFilters = computed(() =>\n settings.value.sourceId !== null ? settings.value.model?.defaultFilters : undefined,\n);\nconst {\n gridState,\n sheetsState,\n searchString,\n filtersState,\n defaultFiltersState,\n resetDefaultFilters,\n} = useTableState(tableState, settings, visibleFilterableColumns, defaultFilters);\nconst sheetsSettings = computed<PlDataTableSheetsSettings>(() => {\n const settingsCopy = { ...settings.value };\n return settingsCopy.sourceId !== null\n ? {\n sheets: settingsCopy.sheets ?? [],\n cachedState: [...sheetsState.value],\n }\n : {\n sheets: [],\n cachedState: [],\n };\n});\ngridOptions.value.initialState = gridState.value;\n\n// Restore proper types erased by AgGrid\nfunction makePartialState(state: GridState): PlDataTableGridStateCore {\n return {\n columnOrder: state.columnOrder as\n | {\n orderedColIds: PlTableColumnIdJson[];\n }\n | undefined,\n sort: state.sort as\n | {\n sortModel: {\n colId: PlTableColumnIdJson;\n sort: \"asc\" | \"desc\";\n }[];\n }\n | undefined,\n columnVisibility: state.columnVisibility as\n | {\n hiddenColIds: PlTableColumnIdJson[];\n }\n | undefined,\n };\n}\n\n// AG Grid returns columnVisibility: undefined when all columns are visible.\n// We need to distinguish \"no state yet\" (use isColumnOptional defaults) from\n// \"user explicitly showed all columns\" (store []). This function normalizes\n// the undefined from AG Grid based on the previous state.\nfunction normalizeColumnVisibility(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n if (partialState.columnVisibility !== undefined) return partialState;\n\n if (prevState.columnVisibility !== undefined) {\n // Had explicit visibility state before → user made all columns visible → store [].\n return { ...partialState, columnVisibility: { hiddenColIds: [] } };\n }\n\n // No previous explicit state → compute defaults from current columns\n // to replicate: hide: hiddenColIds?.includes(colId) ?? isColumnOptional(spec.spec)\n const defaultHidden = getDefaultHiddenColIds(api, columnsMeta);\n if (defaultHidden.length > 0) {\n return { ...partialState, columnVisibility: { hiddenColIds: defaultHidden } };\n }\n\n return partialState;\n}\n\n/** Sidecar display meta is only on the `model` branch of the settings union. */\nfunction currentColumnsMeta(): PlDataTableColumnsMeta | undefined {\n return \"model\" in props.settings ? props.settings.model?.columnsMeta : undefined;\n}\n\nfunction getDefaultHiddenColIds(\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlTableColumnIdJson[] {\n const cols = api.getAllGridColumns();\n if (!cols) return [];\n\n return cols\n .filter((col) => {\n const spec = col.getColDef().context as PTableColumnSpec | undefined;\n return (\n spec !== undefined &&\n spec.type === \"column\" &&\n effectiveVisibility(spec, columnsMeta) === \"optional\"\n );\n })\n .map((col) => col.getColId() as PlTableColumnIdJson);\n}\n\n// Normalize columnVisibility for comparison: undefined and { hiddenColIds: [] } are equivalent.\nfunction stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {\n const cv = state.columnVisibility;\n const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;\n return { ...state, columnVisibility: normalizedCv };\n}\n\n// Reload AgGrid when new state arrives from server\nconst reloadKey = ref(0);\nwatch(\n () => [gridApi.value, gridState.value] as const,\n ([gridApi, gridState]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n const selfState = makePartialState(gridApi.getState());\n if (\n !isJsonEqual(gridState, {}) &&\n !isJsonEqual(stateForReloadCompare(gridState), stateForReloadCompare(selfState))\n ) {\n isReloading = true;\n gridOptions.value.initialState = gridState;\n ++reloadKey.value;\n nextTick(() => {\n isReloading = false;\n });\n }\n },\n);\n\n// Make cellRendererSelector reactive\nconst cellRendererSelector = computed(() => props.cellRendererSelector ?? null);\nwatch(\n () => [gridApi.value, cellRendererSelector.value] as const,\n ([gridApi, cellRendererSelector]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.setGridOption(\"defaultColDef\", {\n ...gridOptions.value.defaultColDef,\n cellRendererSelector: cellRendererSelector ?? undefined,\n });\n },\n);\n\ndefineExpose<PlAgDataTableV2Controller>({\n focusRow: async (rowKey) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n return ensureNodeVisible(gridApi, (row) => isJsonEqual(row.data?.axesKey, rowKey));\n },\n updateSelection: async ({ axesSpec, selectedKeys }) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n const axes = selection.value?.axesSpec;\n if (!axes || axes.length !== axesSpec.length) return false;\n\n const mapping = axesSpec.map((spec) => {\n const id = getAxisId(spec);\n return axes.findIndex((axis) => matchAxisId(axis, id));\n });\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) return false;\n\n const selectedNodes = selectedKeys.map((key) =>\n canonicalizeJson<PlTableRowId>(mapping.map((index) => key[index])),\n );\n const oldSelectedKeys = gridApi.getServerSideSelectionState()?.toggledNodes ?? [];\n if (!isJsonEqual(oldSelectedKeys, selectedNodes)) {\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes,\n });\n\n // wait for `onSelectionChanged` to update `selection` model\n const scope = effectScope();\n const { resolve, promise } = Promise.withResolvers();\n scope.run(() => watch(selection, resolve, { once: true }));\n try {\n await promiseTimeout(promise, 500);\n } catch {\n return false;\n } finally {\n scope.stop();\n }\n }\n return true;\n },\n});\n\n// Update AgGrid when settings change\nconst defaultSelection = createPlSelectionModel();\nlet oldSettings: PlDataTableSettingsV2 | null = null;\nconst generation = ref(0);\nwatch(\n () => [gridApi.value, settings.value] as const,\n ([gridApi, settings]) => {\n // Wait for AgGrid reinitialization, gridApi will eventually become initialized\n if (!gridApi || gridApi.isDestroyed()) return;\n // Verify that this is not a false watch trigger\n if (isJsonEqual(settings, oldSettings)) return;\n ++generation.value;\n try {\n // Hide no rows overlay if it is shown, or else loading overlay will not be shown\n gridApi.hideOverlay();\n dataRenderedTracker.reset();\n\n // No data source selected -> reset state to default\n if (settings.sourceId === null) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: settings.pending ? \"running\" : \"not-ready\",\n } satisfies PlAgOverlayLoadingParams,\n columnDefs: undefined,\n serverSideDatasource: undefined,\n });\n if (selection.value) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n return;\n }\n\n if (\n settings.model?.fullTableHandle === undefined ||\n settings.model?.visibleTableHandle === undefined\n ) {\n return;\n }\n\n // Data source changed -> show full page loader, clear selection\n if (settings.sourceId !== oldSettings?.sourceId) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: \"loading\",\n } satisfies PlAgOverlayLoadingParams,\n });\n if (selection.value && oldSettings?.sourceId) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n }\n\n // Model updated -> show skeletons instead of data\n const sourceChanged =\n settings.model?.sourceId && settings.model.sourceId !== settings.sourceId;\n if (!settings.model || sourceChanged) {\n const state = gridApi.getServerSideGroupLevelState();\n const rowCount = !sourceChanged && state.length > 0 ? state[0].rowCount : 1;\n return gridApi.updateGridOptions({\n serverSideDatasource: {\n getRows: (params) => {\n params.success({ rowData: [], rowCount });\n },\n },\n });\n }\n\n // Model ready -> calculate new state\n const stateGeneration = generation.value;\n calculateGridOptions({\n generation,\n pfDriver: getRawPlatformaInstance().pFrameDriver,\n fullTableHandle: settings.model?.fullTableHandle,\n visibleTableHandle: settings.model?.visibleTableHandle,\n columnsMeta: settings.model?.columnsMeta,\n sheets: settings.sheets ?? [],\n dataRenderedTracker,\n hiddenColIds: gridState.value.columnVisibility?.hiddenColIds,\n cellButtonAxisParams: {\n showCellButtonForAxisId: props.showCellButtonForAxisId,\n cellButtonInvokeRowsOnDoubleClick: props.cellButtonInvokeRowsOnDoubleClick,\n trigger: (key?: PTableKey) => emit(\"cellButtonClicked\", key),\n } satisfies PlAgCellButtonAxisParams,\n })\n .then((result) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n const { axesSpec, ...options } = result;\n gridApi.updateGridOptions({\n ...options,\n });\n if (selection.value) {\n // Update selection if axesSpec changed, as order of axes may have changed and so we need to remap selected keys\n const { axesSpec: oldAxesSpec, selectedKeys: oldSelectedKeys } = selection.value;\n if (!isJsonEqual(oldAxesSpec, axesSpec)) {\n if (!oldAxesSpec || axesSpec.length !== oldAxesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const mapping = oldAxesSpec\n .map(getAxisId)\n .map((id) => axesSpec.findIndex((axis) => matchAxisId(axis, id)));\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const selectedNodes = oldSelectedKeys.map((key) =>\n mapping.map((index) => key[index]),\n );\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: selectedNodes };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes.map((key) => canonicalizeJson<PlTableRowId>(key)),\n });\n }\n }\n })\n .catch((error: unknown) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n if (isAbortError(error)) return;\n console.trace(error);\n })\n .finally(() => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n gridApi.updateGridOptions({\n loading: false,\n });\n });\n dataRenderedTracker.promise.then(() => emit(\"newDataRendered\"));\n } catch (error: unknown) {\n console.trace(error);\n } finally {\n oldSettings = settings;\n }\n },\n);\n\nwatch(\n () => ({\n gridApi: gridApi.value,\n loadingText: props.loadingText,\n runningText: props.runningText,\n notReadyText: props.notReadyText,\n noRowsText: props.noRowsText,\n }),\n ({ gridApi, loadingText, runningText, notReadyText, noRowsText }) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.updateGridOptions({\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n loadingText,\n runningText,\n notReadyText,\n },\n noRowsOverlayComponentParams: {\n ...gridOptions.value.noRowsOverlayComponentParams,\n text: noRowsText,\n },\n });\n },\n);\n\nwatchEffect(() => {\n if (!gridApi.value || gridApi.value?.isDestroyed()) return;\n gridApi.value.updateGridOptions({\n statusBar: gridOptions.value.loading\n ? undefined\n : {\n statusPanels: [{ statusPanel: PlAgRowCount, align: \"left\" }],\n },\n });\n});\n</script>\n\n<template>\n <div :class=\"$style.container\">\n <PlAgGridColumnManager v-if=\"gridApi && !disableColumnsPanel\" :api=\"gridApi\" />\n <PlTableFiltersV2\n v-if=\"!disableFiltersPanel\"\n :filters=\"filtersState\"\n :default-filters=\"defaultFiltersState\"\n :pframe-handle=\"'model' in settings ? settings?.model?.fullPframeHandle : undefined\"\n :columns=\"filterableColumns\"\n @updateFilters=\"(v) => (filtersState = v)\"\n @resetDefaultFilters=\"resetDefaultFilters\"\n @updateDefaultFilters=\"(v) => (defaultFiltersState = v)\"\n v-model=\"filtersState\"\n />\n <PlAgCsvExporter\n v-if=\"gridApi && showExportButton\"\n :api=\"gridApi\"\n :table-handle=\"'model' in settings ? settings?.model?.visibleTableHandle : undefined\"\n />\n <PlAgDataTableSheets v-model=\"sheetsState\" :settings=\"sheetsSettings\">\n <template v-if=\"$slots['before-sheets']\" #before>\n <slot name=\"before-sheets\" />\n </template>\n <template v-if=\"$slots['after-sheets']\" #after>\n <slot name=\"after-sheets\" />\n </template>\n </PlAgDataTableSheets>\n <PlTableFastSearch v-model=\"searchString\" />\n <AgGridVue\n :key=\"reloadKey\"\n :theme=\"AgGridTheme\"\n :class=\"$style.grid\"\n :grid-options=\"gridOptions\"\n />\n </div>\n</template>\n\n<style lang=\"css\" module>\n.container {\n display: flex;\n flex-direction: column;\n height: 100%;\n gap: 12px;\n}\n\n.grid {\n flex: 1;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"PlAgDataTableV2.js","names":[],"sources":["../../../src/components/PlAgDataTable/PlAgDataTableV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { promiseTimeout, isJsonEqual } from \"@milaboratories/helpers\";\nimport type {\n AxisId,\n PlDataTableColumnsMeta,\n PlDataTableGridStateCore,\n PlDataTableStateV2,\n PlSelectionModel,\n PlTableColumnIdJson,\n PTableColumnSpec,\n PTableKey,\n} from \"@platforma-sdk/model\";\nimport {\n getRawPlatformaInstance,\n createPlSelectionModel,\n matchAxisId,\n getAxisId,\n canonicalizeJson,\n isAbortError,\n} from \"@platforma-sdk/model\";\nimport type { CellRendererSelectorFunc, GridApi, GridState } from \"ag-grid-enterprise\";\nimport { AgGridVue } from \"ag-grid-vue3\";\nimport { computed, effectScope, nextTick, ref, toRefs, watch, watchEffect } from \"vue\";\nimport PlAgCsvExporter from \"../PlAgCsvExporter/PlAgCsvExporter.vue\";\nimport { PlAgGridColumnManager } from \"../PlAgGridColumnManager\";\nimport PlTableFiltersV2 from \"../PlTableFilters/PlTableFiltersV2.vue\";\nimport { PlTableFastSearch } from \"../PlTableFastSearch\";\nimport PlAgDataTableSheets from \"./PlAgDataTableSheets.vue\";\nimport PlAgRowCount from \"./PlAgRowCount.vue\";\nimport { DeferredCircular, ensureNodeVisible } from \"./sources/focus-row\";\nimport { PlAgDataTableRowNumberColId } from \"./sources/row-number\";\nimport type { PlAgCellButtonAxisParams } from \"./sources/table-source-v2\";\nimport { calculateGridOptions, effectiveVisibility } from \"./sources/table-source-v2\";\nimport { useTableState } from \"./sources/table-state-v2\";\nimport type {\n PlAgDataTableV2Controller,\n PlAgDataTableV2Row,\n PlAgOverlayLoadingParams,\n PlAgOverlayNoRowsParams,\n PlDataTableSettingsV2,\n PlDataTableSheetsSettings,\n PlTableRowId,\n} from \"./types\";\nimport { useFilterableColumns } from \"./compositions/useFilterableColumns\";\nimport { useGrid } from \"./compositions/useGrid\";\nimport { AgGridTheme } from \"../../AgGridVue/AgGridTheme\";\n\nconst tableState = defineModel<PlDataTableStateV2>({\n required: true,\n});\n/** Warning: selection model value updates are ignored, use updateSelection instead */\nconst selection = defineModel<PlSelectionModel>(\"selection\");\nconst props = defineProps<{\n /** Required component settings */\n settings: Readonly<PlDataTableSettingsV2>;\n\n /**\n * The disableColumnsPanel prop controls the display of a button that activates\n * the columns management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableColumnsPanel?: boolean;\n\n /**\n * The disableFiltersPanel prop controls the display of a button that activates\n * the filters management panel in the table. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n disableFiltersPanel?: boolean;\n\n /**\n * The showExportButton prop controls the display of a button that allows\n * to export table data in CSV format. To make the button functional\n * and visible, you must also include the PlAgDataTableToolsPanel component in your layout.\n * This component serves as the target for teleporting the button.\n */\n showExportButton?: boolean;\n\n /**\n * The AxisId property is used to configure and display the PlAgTextAndButtonCell component\n */\n showCellButtonForAxisId?: AxisId;\n\n /**\n * If cellButtonInvokeRowsOnDoubleClick = true, clicking a button inside the row\n * triggers the doubleClick event for the entire row.\n *\n * If cellButtonInvokeRowsOnDoubleClick = false, the doubleClick event for the row\n * is not triggered, but will triggered cellButtonClicked event with (key: PTableRowKey) argument.\n */\n cellButtonInvokeRowsOnDoubleClick?: boolean;\n\n /** @see {@link PlAgOverlayLoadingParams.loadingText} */\n loadingText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.runningText} */\n runningText?: string;\n\n /** @see {@link PlAgOverlayLoadingParams.notReadyText} */\n notReadyText?: string;\n\n /** @see {@link PlAgOverlayNoRowsParams.text} */\n noRowsText?: string;\n\n /**\n * Callback to override the default renderer for a given cell.\n * @see https://www.ag-grid.com/vue-data-grid/component-cell-renderer/#dynamic-component-selection\n */\n cellRendererSelector?: CellRendererSelectorFunc<PlAgDataTableV2Row>;\n}>();\nconst { settings } = toRefs(props);\nconst emit = defineEmits<{\n rowDoubleClicked: [key?: PTableKey];\n cellButtonClicked: [key?: PTableKey];\n newDataRendered: [];\n}>();\n\nconst dataRenderedTracker = new DeferredCircular<GridApi<PlAgDataTableV2Row>>();\nconst { gridApi, gridOptions } = useGrid({\n selection,\n noRowsText: props.noRowsText,\n runningText: props.runningText,\n loadingText: props.loadingText,\n notReadyText: props.notReadyText,\n cellRendererSelector: props.cellRendererSelector,\n});\nlet isReloading = false;\ngridOptions.value.onGridPreDestroyed = (event) => {\n if (!isReloading) {\n gridOptions.value.initialState = gridState.value = normalizeColumnVisibility(\n makePartialState(event.api.getState()),\n gridState.value,\n event.api,\n getColumnsMeta(),\n );\n }\n gridApi.value = null;\n};\ngridOptions.value.onRowDoubleClicked = (event) => {\n if (event.data && event.data.axesKey) emit(\"rowDoubleClicked\", event.data.axesKey);\n};\ngridOptions.value.onStateUpdated = (event) => {\n const partialState = normalizeColumnVisibility(\n makePartialState(event.state),\n gridState.value,\n event.api,\n getColumnsMeta(),\n );\n // We have to keep initialState synchronized with gridState for gridState recovery after key updating.\n gridOptions.value.initialState = gridState.value = partialState;\n\n if (!isJsonEqual(event.sources, [\"columnSizing\"])) {\n event.api.autoSizeColumns(\n event.api\n .getAllDisplayedColumns()\n .filter((column) => column.getColId() !== PlAgDataTableRowNumberColId),\n );\n }\n};\n\nconst [filterableColumns, visibleFilterableColumns] = useFilterableColumns(\n () => settings.value.sourceId,\n () => gridOptions.value.columnDefs ?? null,\n);\nconst defaultFilters = computed(() =>\n settings.value.sourceId !== null ? settings.value.model?.defaultFilters : undefined,\n);\nconst {\n gridState,\n sheetsState,\n searchString,\n filtersState,\n defaultFiltersState,\n resetDefaultFilters,\n} = useTableState(tableState, settings, visibleFilterableColumns, defaultFilters);\nconst sheetsSettings = computed<PlDataTableSheetsSettings>(() => {\n const settingsCopy = { ...settings.value };\n return settingsCopy.sourceId !== null\n ? {\n sheets: settingsCopy.sheets ?? [],\n cachedState: [...sheetsState.value],\n }\n : {\n sheets: [],\n cachedState: [],\n };\n});\ngridOptions.value.initialState = gridState.value;\n\n// Restore proper types erased by AgGrid\nfunction makePartialState(state: GridState): PlDataTableGridStateCore {\n return {\n columnOrder: state.columnOrder as\n | {\n orderedColIds: PlTableColumnIdJson[];\n }\n | undefined,\n sort: state.sort as\n | {\n sortModel: {\n colId: PlTableColumnIdJson;\n sort: \"asc\" | \"desc\";\n }[];\n }\n | undefined,\n columnVisibility: state.columnVisibility as\n | {\n hiddenColIds: PlTableColumnIdJson[];\n }\n | undefined,\n };\n}\n\n// AG Grid returns columnVisibility: undefined when all columns are visible.\n// We need to distinguish \"no state yet\" (use isColumnOptional defaults) from\n// \"user explicitly showed all columns\" (store []). This function normalizes\n// the undefined from AG Grid based on the previous state.\nfunction normalizeColumnVisibility(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n if (partialState.columnVisibility !== undefined) return partialState;\n\n if (prevState.columnVisibility !== undefined) {\n // Had explicit visibility state before → user made all columns visible → store [].\n return { ...partialState, columnVisibility: { hiddenColIds: [] } };\n }\n\n // No previous explicit state → compute defaults from current columns\n // to replicate: hide: hiddenColIds?.includes(colId) ?? isColumnOptional(spec.spec)\n const defaultHidden = getDefaultHiddenColIds(api, columnsMeta);\n if (defaultHidden.length > 0) {\n return { ...partialState, columnVisibility: { hiddenColIds: defaultHidden } };\n }\n\n return partialState;\n}\n\n/** Sidecar display meta is only on the `model` branch of the settings union. */\nfunction getColumnsMeta(): PlDataTableColumnsMeta | undefined {\n return \"model\" in props.settings ? props.settings.model?.columnsMeta : undefined;\n}\n\nfunction getDefaultHiddenColIds(\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlTableColumnIdJson[] {\n const cols = api.getAllGridColumns();\n if (!cols) return [];\n\n return cols\n .filter((col) => {\n const spec = col.getColDef().context as PTableColumnSpec | undefined;\n return (\n spec !== undefined &&\n spec.type === \"column\" &&\n effectiveVisibility(spec, columnsMeta) === \"optional\"\n );\n })\n .map((col) => col.getColId() as PlTableColumnIdJson);\n}\n\n// Normalize columnVisibility for comparison: undefined and { hiddenColIds: [] } are equivalent.\nfunction stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {\n const cv = state.columnVisibility;\n const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;\n return { ...state, columnVisibility: normalizedCv };\n}\n\n// Reload AgGrid when new state arrives from server\nconst reloadKey = ref(0);\nwatch(\n () => [gridApi.value, gridState.value] as const,\n ([gridApi, gridState]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n const selfState = makePartialState(gridApi.getState());\n if (\n !isJsonEqual(gridState, {}) &&\n !isJsonEqual(stateForReloadCompare(gridState), stateForReloadCompare(selfState))\n ) {\n isReloading = true;\n gridOptions.value.initialState = gridState;\n ++reloadKey.value;\n nextTick(() => {\n isReloading = false;\n });\n }\n },\n);\n\n// Make cellRendererSelector reactive\nconst cellRendererSelector = computed(() => props.cellRendererSelector ?? null);\nwatch(\n () => [gridApi.value, cellRendererSelector.value] as const,\n ([gridApi, cellRendererSelector]) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.setGridOption(\"defaultColDef\", {\n ...gridOptions.value.defaultColDef,\n cellRendererSelector: cellRendererSelector ?? undefined,\n });\n },\n);\n\ndefineExpose<PlAgDataTableV2Controller>({\n focusRow: async (rowKey) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n return ensureNodeVisible(gridApi, (row) => isJsonEqual(row.data?.axesKey, rowKey));\n },\n updateSelection: async ({ axesSpec, selectedKeys }) => {\n const gridApi = await dataRenderedTracker.promise;\n if (gridApi.isDestroyed()) return false;\n\n const axes = selection.value?.axesSpec;\n if (!axes || axes.length !== axesSpec.length) return false;\n\n const mapping = axesSpec.map((spec) => {\n const id = getAxisId(spec);\n return axes.findIndex((axis) => matchAxisId(axis, id));\n });\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) return false;\n\n const selectedNodes = selectedKeys.map((key) =>\n canonicalizeJson<PlTableRowId>(mapping.map((index) => key[index])),\n );\n const oldSelectedKeys = gridApi.getServerSideSelectionState()?.toggledNodes ?? [];\n if (!isJsonEqual(oldSelectedKeys, selectedNodes)) {\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes,\n });\n\n // wait for `onSelectionChanged` to update `selection` model\n const scope = effectScope();\n const { resolve, promise } = Promise.withResolvers();\n scope.run(() => watch(selection, resolve, { once: true }));\n try {\n await promiseTimeout(promise, 500);\n } catch {\n return false;\n } finally {\n scope.stop();\n }\n }\n return true;\n },\n});\n\n// Update AgGrid when settings change\nconst defaultSelection = createPlSelectionModel();\nlet oldSettings: PlDataTableSettingsV2 | null = null;\nconst generation = ref(0);\nwatch(\n () => [gridApi.value, settings.value] as const,\n ([gridApi, settings]) => {\n // Wait for AgGrid reinitialization, gridApi will eventually become initialized\n if (!gridApi || gridApi.isDestroyed()) return;\n // Verify that this is not a false watch trigger\n if (isJsonEqual(settings, oldSettings)) return;\n ++generation.value;\n try {\n // Hide no rows overlay if it is shown, or else loading overlay will not be shown\n gridApi.hideOverlay();\n dataRenderedTracker.reset();\n\n // No data source selected -> reset state to default\n if (settings.sourceId === null) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: settings.pending ? \"running\" : \"not-ready\",\n } satisfies PlAgOverlayLoadingParams,\n columnDefs: undefined,\n serverSideDatasource: undefined,\n });\n if (selection.value) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n return;\n }\n\n if (\n settings.model?.fullTableHandle === undefined ||\n settings.model?.visibleTableHandle === undefined\n ) {\n return;\n }\n\n // Data source changed -> show full page loader, clear selection\n if (settings.sourceId !== oldSettings?.sourceId) {\n gridApi.updateGridOptions({\n loading: true,\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n variant: \"loading\",\n } satisfies PlAgOverlayLoadingParams,\n });\n if (selection.value && oldSettings?.sourceId) {\n if (selection.value && !isJsonEqual(selection.value, defaultSelection)) {\n selection.value = createPlSelectionModel();\n }\n gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n }\n\n // Model updated -> show skeletons instead of data\n const sourceChanged =\n settings.model?.sourceId && settings.model.sourceId !== settings.sourceId;\n if (!settings.model || sourceChanged) {\n const state = gridApi.getServerSideGroupLevelState();\n const rowCount = !sourceChanged && state.length > 0 ? state[0].rowCount : 1;\n return gridApi.updateGridOptions({\n serverSideDatasource: {\n getRows: (params) => {\n params.success({ rowData: [], rowCount });\n },\n },\n });\n }\n\n // Model ready -> calculate new state\n const stateGeneration = generation.value;\n calculateGridOptions({\n generation,\n pfDriver: getRawPlatformaInstance().pFrameDriver,\n fullTableHandle: settings.model?.fullTableHandle,\n visibleTableHandle: settings.model?.visibleTableHandle,\n columnsMeta: settings.model?.columnsMeta,\n sheets: settings.sheets ?? [],\n dataRenderedTracker,\n hiddenColIds: gridState.value.columnVisibility?.hiddenColIds,\n cellButtonAxisParams: {\n showCellButtonForAxisId: props.showCellButtonForAxisId,\n cellButtonInvokeRowsOnDoubleClick: props.cellButtonInvokeRowsOnDoubleClick,\n trigger: (key?: PTableKey) => emit(\"cellButtonClicked\", key),\n } satisfies PlAgCellButtonAxisParams,\n })\n .then((result) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n const { axesSpec, ...options } = result;\n gridApi.updateGridOptions({\n ...options,\n });\n if (selection.value) {\n // Update selection if axesSpec changed, as order of axes may have changed and so we need to remap selected keys\n const { axesSpec: oldAxesSpec, selectedKeys: oldSelectedKeys } = selection.value;\n if (!isJsonEqual(oldAxesSpec, axesSpec)) {\n if (!oldAxesSpec || axesSpec.length !== oldAxesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const mapping = oldAxesSpec\n .map(getAxisId)\n .map((id) => axesSpec.findIndex((axis) => matchAxisId(axis, id)));\n const mappingSet = new Set(mapping);\n if (mappingSet.has(-1) || mappingSet.size !== axesSpec.length) {\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: [] };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: [],\n });\n }\n\n const selectedNodes = oldSelectedKeys.map((key) =>\n mapping.map((index) => key[index]),\n );\n const newSelection: PlSelectionModel = { axesSpec, selectedKeys: selectedNodes };\n if (!isJsonEqual(selection.value, newSelection)) {\n selection.value = newSelection;\n }\n return gridApi.setServerSideSelectionState({\n selectAll: false,\n toggledNodes: selectedNodes.map((key) => canonicalizeJson<PlTableRowId>(key)),\n });\n }\n }\n })\n .catch((error: unknown) => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n if (isAbortError(error)) return;\n console.trace(error);\n })\n .finally(() => {\n if (gridApi.isDestroyed() || stateGeneration !== generation.value) return;\n gridApi.updateGridOptions({\n loading: false,\n });\n });\n dataRenderedTracker.promise.then(() => emit(\"newDataRendered\"));\n } catch (error: unknown) {\n console.trace(error);\n } finally {\n oldSettings = settings;\n }\n },\n);\n\nwatch(\n () => ({\n gridApi: gridApi.value,\n loadingText: props.loadingText,\n runningText: props.runningText,\n notReadyText: props.notReadyText,\n noRowsText: props.noRowsText,\n }),\n ({ gridApi, loadingText, runningText, notReadyText, noRowsText }) => {\n if (!gridApi || gridApi.isDestroyed()) return;\n gridApi.updateGridOptions({\n loadingOverlayComponentParams: {\n ...gridOptions.value.loadingOverlayComponentParams,\n loadingText,\n runningText,\n notReadyText,\n },\n noRowsOverlayComponentParams: {\n ...gridOptions.value.noRowsOverlayComponentParams,\n text: noRowsText,\n },\n });\n },\n);\n\nwatchEffect(() => {\n if (!gridApi.value || gridApi.value?.isDestroyed()) return;\n gridApi.value.updateGridOptions({\n statusBar: gridOptions.value.loading\n ? undefined\n : {\n statusPanels: [{ statusPanel: PlAgRowCount, align: \"left\" }],\n },\n });\n});\n</script>\n\n<template>\n <div :class=\"$style.container\">\n <PlAgGridColumnManager v-if=\"gridApi && !disableColumnsPanel\" :api=\"gridApi\" />\n <PlTableFiltersV2\n v-if=\"!disableFiltersPanel\"\n :filters=\"filtersState\"\n :default-filters=\"defaultFiltersState\"\n :pframe-handle=\"'model' in settings ? settings?.model?.fullPframeHandle : undefined\"\n :columns=\"filterableColumns\"\n @updateFilters=\"(v) => (filtersState = v)\"\n @resetDefaultFilters=\"resetDefaultFilters\"\n @updateDefaultFilters=\"(v) => (defaultFiltersState = v)\"\n v-model=\"filtersState\"\n />\n <PlAgCsvExporter\n v-if=\"gridApi && showExportButton\"\n :api=\"gridApi\"\n :table-handle=\"'model' in settings ? settings?.model?.visibleTableHandle : undefined\"\n :columns-meta=\"getColumnsMeta()\"\n />\n <PlAgDataTableSheets v-model=\"sheetsState\" :settings=\"sheetsSettings\">\n <template v-if=\"$slots['before-sheets']\" #before>\n <slot name=\"before-sheets\" />\n </template>\n <template v-if=\"$slots['after-sheets']\" #after>\n <slot name=\"after-sheets\" />\n </template>\n </PlAgDataTableSheets>\n <PlTableFastSearch v-model=\"searchString\" />\n <AgGridVue\n :key=\"reloadKey\"\n :theme=\"AgGridTheme\"\n :class=\"$style.grid\"\n :grid-options=\"gridOptions\"\n />\n </div>\n</template>\n\n<style lang=\"css\" module>\n.container {\n display: flex;\n flex-direction: column;\n height: 100%;\n gap: 12px;\n}\n\n.grid {\n flex: 1;\n}\n</style>\n"],"mappings":""}