@platforma-sdk/ui-vue 1.84.0 → 1.84.2
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.
- package/.turbo/turbo-build.log +12 -11
- package/.turbo/turbo-formatter$colon$check.log +2 -2
- package/.turbo/turbo-linter$colon$check.log +2 -2
- package/.turbo/turbo-types$colon$check.log +1 -1
- package/CHANGELOG.md +123 -0
- package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +175 -180
- package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
- package/dist/components/PlAgDataTable/__tests__/PlAgDataTableV2.reload.jsdomtest.d.ts +2 -0
- package/dist/components/PlAgDataTable/__tests__/PlAgDataTableV2.reload.jsdomtest.d.ts.map +1 -0
- package/dist/components/PlAgDataTable/sources/grid-state.d.ts +22 -0
- package/dist/components/PlAgDataTable/sources/grid-state.d.ts.map +1 -0
- package/dist/components/PlAgDataTable/sources/grid-state.js +39 -0
- package/dist/components/PlAgDataTable/sources/grid-state.js.map +1 -0
- package/dist/components/PlAgDataTable/sources/grid-state.test.d.ts +2 -0
- package/dist/components/PlAgDataTable/sources/grid-state.test.d.ts.map +1 -0
- package/dist/components/PlAgDataTable/sources/table-state-v2.d.ts.map +1 -1
- package/dist/components/PlAgDataTable/sources/table-state-v2.js +1 -0
- package/dist/components/PlAgDataTable/sources/table-state-v2.js.map +1 -1
- package/package.json +7 -7
- package/src/components/PlAgDataTable/PlAgDataTableV2.vue +28 -16
- package/src/components/PlAgDataTable/__tests__/PlAgDataTableV2.reload.jsdomtest.ts +222 -0
- package/src/components/PlAgDataTable/sources/grid-state.test.ts +94 -0
- package/src/components/PlAgDataTable/sources/grid-state.ts +110 -0
- package/src/components/PlAgDataTable/sources/table-state-v2.ts +7 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -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.84.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.84.2 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...
|
|
@@ -33,11 +33,11 @@ Building browser-lib project...
|
|
|
33
33
|
[7m4[0m export function useGridColumns(props: { api: GridApi }) {
|
|
34
34
|
[7m [0m [91m ~~~~~~~~~~~~~~[0m
|
|
35
35
|
|
|
36
|
-
[2K
|
|
36
|
+
[2K
|
|
37
37
|
rendering chunks...
|
|
38
38
|
|
|
39
39
|
[unplugin:dts] Start generate declaration files...
|
|
40
|
-
[unplugin:dts] Declaration files built in
|
|
40
|
+
[unplugin:dts] Declaration files built in 4268ms.
|
|
41
41
|
|
|
42
42
|
computing gzip size...
|
|
43
43
|
dist/components/PlAnnotations/components/PlAnnotations.vue?vue&type=style&index=0&lang.css 0.04 kB │ gzip: 0.06 kB
|
|
@@ -142,7 +142,7 @@ dist/components/PlAdvancedFilter/FilterEditor.js
|
|
|
142
142
|
dist/components/PlAdvancedFilter/OperandButton.js 0.35 kB │ gzip: 0.24 kB │ map: 1.31 kB
|
|
143
143
|
dist/components/PlAnnotations/components/FilterSidebar.js 0.35 kB │ gzip: 0.24 kB │ map: 5.42 kB
|
|
144
144
|
dist/components/PlAnnotations/components/PlAnnotations.js 0.35 kB │ gzip: 0.24 kB │ map: 3.11 kB
|
|
145
|
-
dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map:
|
|
145
|
+
dist/components/PlAgDataTable/PlAgDataTableV2.js 0.35 kB │ gzip: 0.25 kB │ map: 25.01 kB
|
|
146
146
|
dist/plugins/Monetization/UserCabinetCard.js 0.35 kB │ gzip: 0.24 kB │ map: 3.00 kB
|
|
147
147
|
dist/components/PlAdvancedFilter/PlAdvancedFilter.js 0.36 kB │ gzip: 0.24 kB │ map: 13.43 kB
|
|
148
148
|
dist/components/PlAgDataTable/PlAgOverlayNoRows.js 0.36 kB │ gzip: 0.21 kB │ map: 0.74 kB
|
|
@@ -194,6 +194,7 @@ dist/components/PlAgCellFile/PlAgCellFile.vue_vue_type_script_setup_true_lang.js
|
|
|
194
194
|
dist/plugins/Monetization/RunStatus.vue_vue_type_script_setup_true_lang.js 1.16 kB │ gzip: 0.62 kB │ map: 3.04 kB
|
|
195
195
|
dist/components/PlAgDataTable/sources/value-rendering.js 1.16 kB │ gzip: 0.56 kB │ map: 3.38 kB
|
|
196
196
|
dist/components/PlAgGridColumnManager/useFilteredItems.js 1.18 kB │ gzip: 0.61 kB │ map: 3.48 kB
|
|
197
|
+
dist/components/PlAgDataTable/sources/grid-state.js 1.19 kB │ gzip: 0.54 kB │ map: 5.61 kB
|
|
197
198
|
dist/lib/util/helpers/dist/objects.js 1.20 kB │ gzip: 0.58 kB │ map: 9.83 kB
|
|
198
199
|
dist/createModel.js 1.22 kB │ gzip: 0.61 kB │ map: 3.11 kB
|
|
199
200
|
dist/utils.js 1.26 kB │ gzip: 0.62 kB │ map: 3.40 kB
|
|
@@ -237,20 +238,20 @@ dist/AgGridVue/useAgGridOptions.js
|
|
|
237
238
|
dist/index.js 4.50 kB │ gzip: 1.36 kB │ map: 0.25 kB
|
|
238
239
|
dist/components/PlTableFilters/PlTableFiltersV2.vue_vue_type_script_setup_true_lang.js 4.90 kB │ gzip: 2.02 kB │ map: 9.61 kB
|
|
239
240
|
dist/components/PlBtnExportArchive/PlBtnExportArchive.vue_vue_type_script_setup_true_lang.js 5.19 kB │ gzip: 2.16 kB │ map: 10.36 kB
|
|
240
|
-
dist/components/PlAgDataTable/sources/table-state-v2.js 5.
|
|
241
|
+
dist/components/PlAgDataTable/sources/table-state-v2.js 5.71 kB │ gzip: 1.83 kB │ map: 20.20 kB
|
|
241
242
|
dist/internal/createAppV3.js 6.19 kB │ gzip: 2.33 kB │ map: 21.40 kB
|
|
242
243
|
dist/components/PlAgDataTable/sources/table-source-v2.js 7.59 kB │ gzip: 2.88 kB │ map: 27.20 kB
|
|
243
244
|
dist/components/PlAdvancedFilter/PlAdvancedFilter.vue_vue_type_script_setup_true_lang.js 8.57 kB │ gzip: 2.60 kB │ map: 18.55 kB
|
|
244
245
|
dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lang.js 10.58 kB │ gzip: 3.14 kB │ map: 24.73 kB
|
|
245
|
-
dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.
|
|
246
|
+
dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.68 kB │ gzip: 3.99 kB │ map: 32.88 kB
|
|
246
247
|
|
|
247
248
|
[33m[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugins. Here is a breakdown:
|
|
248
|
-
- sourcemaps (
|
|
249
|
-
- unplugin-dts (
|
|
250
|
-
- vite:vue (
|
|
251
|
-
- vite:css-post (
|
|
249
|
+
- sourcemaps (26%)
|
|
250
|
+
- unplugin-dts (25%)
|
|
251
|
+
- vite:vue (14%)
|
|
252
|
+
- vite:css-post (12%)
|
|
252
253
|
- vite:css (8%)
|
|
253
254
|
See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
254
255
|
[39m
|
|
255
|
-
[32m✓ built in
|
|
256
|
+
[32m✓ built in 5.07s[39m
|
|
256
257
|
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.84.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.84.2 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
|
|
11
|
+
Finished in 1756ms on 132 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.84.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.84.2 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
|
|
9
|
+
Finished in 33ms on 115 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.84.
|
|
3
|
+
> @platforma-sdk/ui-vue@1.84.2 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-ts6.cjs --noEmit --project ./tsconfig.json --customConditions default
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,128 @@
|
|
|
1
1
|
# @platforma-sdk/ui-vue
|
|
2
2
|
|
|
3
|
+
## 1.84.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2ce7743: Make `PlAgDataTableV2`'s reload comparison converge by construction, and drop
|
|
8
|
+
the burst limit that was standing in for it.
|
|
9
|
+
|
|
10
|
+
The comparison decides whether to destroy and rebuild the grid around the stored
|
|
11
|
+
state, so it must only ask for things a rebuild can deliver. It was asking for
|
|
12
|
+
two kinds of thing that no rebuild can: a field the stored state does not
|
|
13
|
+
express (AG Grid always reports its whole state), and an id the grid does not
|
|
14
|
+
have — state outlives column sets, so a sort on a column a later run dropped, or
|
|
15
|
+
hidden ids saved against different columns, could never be satisfied. Either one
|
|
16
|
+
turned the watch into an engine for endless rebuilds.
|
|
17
|
+
|
|
18
|
+
Both are now filtered out before comparing: the stored state is reduced to the
|
|
19
|
+
columns the grid actually has, and only the fields it still expresses are
|
|
20
|
+
compared. A stored column order is judged on the relative order of the columns
|
|
21
|
+
it names, so where the grid puts the rest is not a disagreement, and the hidden
|
|
22
|
+
set is compared as a set.
|
|
23
|
+
|
|
24
|
+
With the comparison unable to ask for the impossible, the "give up after three
|
|
25
|
+
reloads a second" limit is gone: it capped the damage of a non-converging
|
|
26
|
+
comparison rather than making one converge, and it would have hidden the next
|
|
27
|
+
such bug instead of surfacing it.
|
|
28
|
+
|
|
29
|
+
- c8c66c5: Stop `PlAgDataTableV2` recreating its grid forever when the stored grid state
|
|
30
|
+
records hidden columns but no column order — around twenty rebuilds a second,
|
|
31
|
+
which strobes the headers and fills the console with AG Grid licence banners.
|
|
32
|
+
|
|
33
|
+
Three things were needed to produce it, and each is addressed:
|
|
34
|
+
|
|
35
|
+
- AG Grid reports an entirely empty state until its columns have been taken into
|
|
36
|
+
its own state, and `normalizeGridState` reads the live columns — so
|
|
37
|
+
normalizing an empty report invented a state (hidden columns, no column order)
|
|
38
|
+
that the grid would never report back. An empty report is now ignored.
|
|
39
|
+
- The reload watch compared the whole state, but the stored state is a partial
|
|
40
|
+
opinion while AG Grid always reports all of it. A stored state with no
|
|
41
|
+
`columnOrder` could therefore never match a grid that has columns, and no
|
|
42
|
+
remount could ever make it. Only the fields the stored state actually
|
|
43
|
+
expresses are compared now (`storedStateApplied`, unit tested).
|
|
44
|
+
- `onStateUpdated` is what would repair the stored state, but AG Grid defers its
|
|
45
|
+
init state event by a timeout and drops it if the grid has since been
|
|
46
|
+
destroyed — so the remounts starved their own cure. The watch now gives up
|
|
47
|
+
after a few reloads in a second: a stale column layout is a far smaller cost
|
|
48
|
+
than an unusable table.
|
|
49
|
+
|
|
50
|
+
Also stops a grid-state change that leaves `pTableParams` untouched — reordering
|
|
51
|
+
columns, say — from being silently dropped: the state cache was spliced in
|
|
52
|
+
place, so the change check compared the new state against itself and skipped the
|
|
53
|
+
write back to the project.
|
|
54
|
+
|
|
55
|
+
- 43e07a3: Fix an infinite grid-remount loop in `PlAgDataTableV2` that recreated the whole
|
|
56
|
+
AG Grid around twenty times a second — strobing the column headers and filling
|
|
57
|
+
the console with AG Grid licence banners — on opening a page whose table had a
|
|
58
|
+
saved grid state, most often right after a block rebuild.
|
|
59
|
+
|
|
60
|
+
`computedCached`'s `writeThrough` made a set visible to readers immediately, but
|
|
61
|
+
the cache was still overwritten whenever the source re-emitted. A set is
|
|
62
|
+
debounced, so between the write and its arrival in the project state the source
|
|
63
|
+
still holds the value the write moved away from; a running block has that state
|
|
64
|
+
pushed back repeatedly, and each push reverted the cache. The stored grid state
|
|
65
|
+
therefore flipped between what the grid had and what the project still held, and
|
|
66
|
+
every flip made the reload watch destroy and recreate the grid — which, because
|
|
67
|
+
the flips never stopped on their own, never converged.
|
|
68
|
+
|
|
69
|
+
A write-through value now stays authoritative until the source reports it back.
|
|
70
|
+
Since `get` maps set values to themselves, that is exactly when the round trip
|
|
71
|
+
has completed; a change from elsewhere arriving mid-flight is dropped in favour
|
|
72
|
+
of the local write that is about to land.
|
|
73
|
+
|
|
74
|
+
- 4058fdb: Follow-ups from review of the `PlAgDataTableV2` reload fixes.
|
|
75
|
+
|
|
76
|
+
An explicitly cleared stored state is an opinion, not silence. Absent and empty
|
|
77
|
+
mean the same thing coming _from_ AG Grid, which omits what carries no
|
|
78
|
+
information, but in the stored state an empty list is the user having cleared the
|
|
79
|
+
sorting or shown every column — and the grid has to be made to match it.
|
|
80
|
+
Normalizing both sides alike meant such a state was read as "nothing to ask for"
|
|
81
|
+
and left the grid stale.
|
|
82
|
+
|
|
83
|
+
A change of data source now moves the generation even when the new model has no
|
|
84
|
+
handles yet. That path starts no calculation, which is why it stopped moving the
|
|
85
|
+
generation — but the calculation in flight belongs to the source being left, and
|
|
86
|
+
must not be allowed to install its columns and datasource under the new one.
|
|
87
|
+
|
|
88
|
+
Taking the loading overlay down belongs to the current generation again, rather
|
|
89
|
+
than to whichever calculation settles last. A superseded calculation could
|
|
90
|
+
otherwise hide an overlay the newer settings had just put up — the "no data
|
|
91
|
+
source" branch raises one and starts no calculation of its own — leaving an empty
|
|
92
|
+
table saying nothing about why. The case that motivated the counter is covered by
|
|
93
|
+
the generation no longer moving on a same-source recomputation.
|
|
94
|
+
|
|
95
|
+
- 0cbb614: Stop `PlAgDataTableV2` leaving its loading overlay up forever when the block it
|
|
96
|
+
shows re-runs. The table would sit on "Loading data…" with no columns, long
|
|
97
|
+
after the block had finished and its table handles were ready.
|
|
98
|
+
|
|
99
|
+
The settings watch bumped a generation counter on every settings change, and a
|
|
100
|
+
calculation that finds the generation moved on writes nothing — including, in
|
|
101
|
+
its `finally`, the one call that takes the loading overlay down. A model between
|
|
102
|
+
handles (what a re-running block produces on its way from one table to the next)
|
|
103
|
+
changes the settings but starts no calculation of its own, so it would supersede
|
|
104
|
+
the calculation in flight and leave nothing behind to clear the overlay.
|
|
105
|
+
|
|
106
|
+
Which options a settled calculation may write is still decided by the
|
|
107
|
+
generation. Taking the overlay down is now decided by whether any calculation is
|
|
108
|
+
still in flight, since the last one to settle is the one that knows nothing
|
|
109
|
+
further is coming. The between-handles path also no longer moves the generation
|
|
110
|
+
at all: it starts no work, so cancelling work is not its business.
|
|
111
|
+
|
|
112
|
+
- Updated dependencies [43e07a3]
|
|
113
|
+
- @milaboratories/uikit@2.15.33
|
|
114
|
+
|
|
115
|
+
## 1.84.1
|
|
116
|
+
|
|
117
|
+
### Patch Changes
|
|
118
|
+
|
|
119
|
+
- Updated dependencies [cadf144]
|
|
120
|
+
- @milaboratories/pl-model-common@1.51.0
|
|
121
|
+
- @milaboratories/columns-collection-driver@0.2.7
|
|
122
|
+
- @milaboratories/pf-spec-driver@1.5.5
|
|
123
|
+
- @platforma-sdk/model@1.84.1
|
|
124
|
+
- @milaboratories/uikit@2.15.32
|
|
125
|
+
|
|
3
126
|
## 1.84.0
|
|
4
127
|
|
|
5
128
|
### Patch Changes
|
|
@@ -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 = normalizeGridState(\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 = normalizeGridState(\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 omits parts of its state that carry no information — columnVisibility\n// when every column is visible, sort when nothing is sorted. The model needs\n// \"no state yet\" (fall back to the block's defaults) to stay distinguishable\n// from \"user explicitly cleared it\" (store []), so both are normalized against\n// the previous state before the state is persisted.\nfunction normalizeGridState(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n return normalizeSort(\n normalizeColumnVisibility(partialState, prevState, api, columnsMeta),\n prevState,\n );\n}\n\n// AG Grid returns sort: undefined when no column is sorted. Left as-is that\n// erases the block's default sorting the moment any unrelated grid state is\n// persisted, so the undefined is only turned into an explicit empty sort model\n// once the user has actually sorted something before.\nfunction normalizeSort(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n): PlDataTableGridStateCore {\n if (partialState.sort !== undefined) return partialState;\n\n if (prevState.sort !== undefined) {\n // Had an explicit sort model before → user cleared the sorting → store [].\n return { ...partialState, sort: { sortModel: [] } };\n }\n\n // Never sorted → leave undefined so the model applies its default sorting.\n return partialState;\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 for comparison: an absent and an empty columnVisibility / sort mean\n// the same thing to AG Grid, and must not count as a state change to reload on.\nfunction stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {\n const cv = state.columnVisibility;\n const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;\n const sort = state.sort;\n const normalizedSort = !sort || sort.sortModel.length === 0 ? undefined : sort;\n return { ...state, columnVisibility: normalizedCv, sort: normalizedSort };\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":""}
|
|
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 { isStoredStateApplied } from \"./sources/grid-state\";\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 = normalizeGridState(\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 reportedState = makePartialState(event.state);\n // AG Grid reports an entirely empty state until its columns have been taken\n // into its own state, and `normalizeGridState` reads the live columns — so\n // normalizing an empty report invents a state (hidden columns, no column\n // order) that the grid will never report back, and the reload watch below\n // would then try to apply it on every remount, forever. Wait for the grid to\n // say something about itself; it always does once its columns are in.\n if (isJsonEqual(reportedState, {})) return;\n\n const partialState = normalizeGridState(\n reportedState,\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 omits parts of its state that carry no information — columnVisibility\n// when every column is visible, sort when nothing is sorted. The model needs\n// \"no state yet\" (fall back to the block's defaults) to stay distinguishable\n// from \"user explicitly cleared it\" (store []), so both are normalized against\n// the previous state before the state is persisted.\nfunction normalizeGridState(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n return normalizeSort(\n normalizeColumnVisibility(partialState, prevState, api, columnsMeta),\n prevState,\n );\n}\n\n// AG Grid returns sort: undefined when no column is sorted. Left as-is that\n// erases the block's default sorting the moment any unrelated grid state is\n// persisted, so the undefined is only turned into an explicit empty sort model\n// once the user has actually sorted something before.\nfunction normalizeSort(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n): PlDataTableGridStateCore {\n if (partialState.sort !== undefined) return partialState;\n\n if (prevState.sort !== undefined) {\n // Had an explicit sort model before → user cleared the sorting → store [].\n return { ...partialState, sort: { sortModel: [] } };\n }\n\n // Never sorted → leave undefined so the model applies its default sorting.\n return partialState;\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// 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 const gridColIds = new Set(\n (gridApi.getAllGridColumns() ?? []).map((column) => column.getColId() as PlTableColumnIdJson),\n );\n if (!isJsonEqual(gridState, {}) && !isStoredStateApplied(gridState, selfState, gridColIds)) {\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 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 ++generation.value;\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 // The model is between handles — it is recomputing. Nothing is started here,\n // so moving the generation would only cancel the calculation in flight and\n // leave whatever it was going to do (not least taking the loading overlay\n // down) undone — which is how the table came to sit on \"Loading data…\"\n // forever. The exception is a change of source: then the calculation in\n // flight belongs to the old one and must not be allowed to land under the\n // new settings.\n if (\n settings.model?.fullTableHandle === undefined ||\n settings.model?.visibleTableHandle === undefined\n ) {\n if (settings.sourceId !== oldSettings?.sourceId) ++generation.value;\n return;\n }\n\n ++generation.value;\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 // Only the current calculation may take the overlay down: a superseded\n // one would hide the overlay the newer settings just put up.\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":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAgDataTableV2.vue_vue_type_style_index_0_lang.module.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 = normalizeGridState(\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 = normalizeGridState(\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 omits parts of its state that carry no information — columnVisibility\n// when every column is visible, sort when nothing is sorted. The model needs\n// \"no state yet\" (fall back to the block's defaults) to stay distinguishable\n// from \"user explicitly cleared it\" (store []), so both are normalized against\n// the previous state before the state is persisted.\nfunction normalizeGridState(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n return normalizeSort(\n normalizeColumnVisibility(partialState, prevState, api, columnsMeta),\n prevState,\n );\n}\n\n// AG Grid returns sort: undefined when no column is sorted. Left as-is that\n// erases the block's default sorting the moment any unrelated grid state is\n// persisted, so the undefined is only turned into an explicit empty sort model\n// once the user has actually sorted something before.\nfunction normalizeSort(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n): PlDataTableGridStateCore {\n if (partialState.sort !== undefined) return partialState;\n\n if (prevState.sort !== undefined) {\n // Had an explicit sort model before → user cleared the sorting → store [].\n return { ...partialState, sort: { sortModel: [] } };\n }\n\n // Never sorted → leave undefined so the model applies its default sorting.\n return partialState;\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 for comparison: an absent and an empty columnVisibility / sort mean\n// the same thing to AG Grid, and must not count as a state change to reload on.\nfunction stateForReloadCompare(state: PlDataTableGridStateCore): PlDataTableGridStateCore {\n const cv = state.columnVisibility;\n const normalizedCv = !cv || cv.hiddenColIds.length === 0 ? undefined : state.columnVisibility;\n const sort = state.sort;\n const normalizedSort = !sort || sort.sortModel.length === 0 ? undefined : sort;\n return { ...state, columnVisibility: normalizedCv, sort: normalizedSort };\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":""}
|
|
1
|
+
{"version":3,"file":"PlAgDataTableV2.vue_vue_type_style_index_0_lang.module.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 { isStoredStateApplied } from \"./sources/grid-state\";\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 = normalizeGridState(\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 reportedState = makePartialState(event.state);\n // AG Grid reports an entirely empty state until its columns have been taken\n // into its own state, and `normalizeGridState` reads the live columns — so\n // normalizing an empty report invents a state (hidden columns, no column\n // order) that the grid will never report back, and the reload watch below\n // would then try to apply it on every remount, forever. Wait for the grid to\n // say something about itself; it always does once its columns are in.\n if (isJsonEqual(reportedState, {})) return;\n\n const partialState = normalizeGridState(\n reportedState,\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 omits parts of its state that carry no information — columnVisibility\n// when every column is visible, sort when nothing is sorted. The model needs\n// \"no state yet\" (fall back to the block's defaults) to stay distinguishable\n// from \"user explicitly cleared it\" (store []), so both are normalized against\n// the previous state before the state is persisted.\nfunction normalizeGridState(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n api: GridApi<PlAgDataTableV2Row>,\n columnsMeta: PlDataTableColumnsMeta | undefined,\n): PlDataTableGridStateCore {\n return normalizeSort(\n normalizeColumnVisibility(partialState, prevState, api, columnsMeta),\n prevState,\n );\n}\n\n// AG Grid returns sort: undefined when no column is sorted. Left as-is that\n// erases the block's default sorting the moment any unrelated grid state is\n// persisted, so the undefined is only turned into an explicit empty sort model\n// once the user has actually sorted something before.\nfunction normalizeSort(\n partialState: PlDataTableGridStateCore,\n prevState: PlDataTableGridStateCore,\n): PlDataTableGridStateCore {\n if (partialState.sort !== undefined) return partialState;\n\n if (prevState.sort !== undefined) {\n // Had an explicit sort model before → user cleared the sorting → store [].\n return { ...partialState, sort: { sortModel: [] } };\n }\n\n // Never sorted → leave undefined so the model applies its default sorting.\n return partialState;\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// 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 const gridColIds = new Set(\n (gridApi.getAllGridColumns() ?? []).map((column) => column.getColId() as PlTableColumnIdJson),\n );\n if (!isJsonEqual(gridState, {}) && !isStoredStateApplied(gridState, selfState, gridColIds)) {\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 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 ++generation.value;\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 // The model is between handles — it is recomputing. Nothing is started here,\n // so moving the generation would only cancel the calculation in flight and\n // leave whatever it was going to do (not least taking the loading overlay\n // down) undone — which is how the table came to sit on \"Loading data…\"\n // forever. The exception is a change of source: then the calculation in\n // flight belongs to the old one and must not be allowed to land under the\n // new settings.\n if (\n settings.model?.fullTableHandle === undefined ||\n settings.model?.visibleTableHandle === undefined\n ) {\n if (settings.sourceId !== oldSettings?.sourceId) ++generation.value;\n return;\n }\n\n ++generation.value;\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 // Only the current calculation may take the overlay down: a superseded\n // one would hide the overlay the newer settings just put up.\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":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlAgDataTableV2.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgDataTable/PlAgDataTableV2.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PlAgDataTableV2.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAgDataTable/PlAgDataTableV2.vue"],"names":[],"mappings":"AAypBA,OAAO,KAAK,EACV,MAAM,EAGN,kBAAkB,EAClB,gBAAgB,EAGhB,SAAS,EACV,MAAM,sBAAsB,CAAC;AAS9B,OAAO,KAAK,EAAE,wBAAwB,EAAsB,MAAM,oBAAoB,CAAC;AAevF,OAAO,KAAK,EACV,yBAAyB,EACzB,kBAAkB,EAGlB,qBAAqB,EAGtB,MAAM,SAAS,CAAC;AAUjB,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,QAAQ,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAE1C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;;;;OAMG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;IAE5C,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,wBAAwB,CAAC,kBAAkB,CAAC,CAAC;CACrE,CAAC;AAsfF,KAAK,gBAAgB,GAAG;IACxB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,sFAAsF;IACtF,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAC9B,CAAC;AAMF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAsKxD,QAAA,IAAI,QAAQ,IAAW,EAAE,QAAQ,IAAY,CAAE;AAC/C,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG,CAAA;CAAE,GACrD;IAAE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG,CAAA;CAAE,CAAC;AAKvD,QAAA,MAAM,UAAU;;;;;;;;;;;;kFAId,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|