@platforma-sdk/ui-vue 1.63.12 → 1.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +38 -32
  2. package/.turbo/turbo-formatter$colon$check.log +2 -2
  3. package/.turbo/turbo-linter$colon$check.log +2 -2
  4. package/.turbo/turbo-types$colon$check.log +1 -1
  5. package/CHANGELOG.md +24 -0
  6. package/dist/components/PlAdvancedFilter/FilterEditor.js.map +1 -1
  7. package/dist/components/PlAdvancedFilter/FilterEditor.style.js.map +1 -1
  8. package/dist/components/PlAdvancedFilter/FilterEditor.vue.d.ts +3 -8
  9. package/dist/components/PlAdvancedFilter/FilterEditor.vue.d.ts.map +1 -1
  10. package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js +164 -151
  11. package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js.map +1 -1
  12. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.js.map +1 -1
  13. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.style.js +8 -7
  14. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.style.js.map +1 -1
  15. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.css +1 -1
  16. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts +24 -8
  17. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts.map +1 -1
  18. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js +176 -110
  19. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js.map +1 -1
  20. package/dist/components/PlAdvancedFilter/types.d.ts +2 -0
  21. package/dist/components/PlAdvancedFilter/types.d.ts.map +1 -1
  22. package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
  23. package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
  24. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
  25. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +116 -109
  26. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
  27. package/dist/components/PlAgDataTable/compositions/useFilterableColumns.js +3 -3
  28. package/dist/components/PlAgDataTable/compositions/useFilterableColumns.js.map +1 -1
  29. package/dist/components/PlAgDataTable/sources/table-source-v2.d.ts +6 -5
  30. package/dist/components/PlAgDataTable/sources/table-source-v2.d.ts.map +1 -1
  31. package/dist/components/PlAgDataTable/sources/table-source-v2.js +122 -88
  32. package/dist/components/PlAgDataTable/sources/table-source-v2.js.map +1 -1
  33. package/dist/components/PlAgDataTable/sources/table-state-v2.d.ts +6 -3
  34. package/dist/components/PlAgDataTable/sources/table-state-v2.d.ts.map +1 -1
  35. package/dist/components/PlAgDataTable/sources/table-state-v2.js +182 -97
  36. package/dist/components/PlAgDataTable/sources/table-state-v2.js.map +1 -1
  37. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.js.map +1 -1
  38. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.style.js.map +1 -1
  39. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue.d.ts.map +1 -1
  40. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue2.js +37 -42
  41. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue2.js.map +1 -1
  42. package/dist/components/PlAgGridColumnManager/useFilteredItems.d.ts +5 -5
  43. package/dist/components/PlAgGridColumnManager/useFilteredItems.d.ts.map +1 -1
  44. package/dist/components/PlAgGridColumnManager/useFilteredItems.js +2 -2
  45. package/dist/components/PlAgGridColumnManager/useFilteredItems.js.map +1 -1
  46. package/dist/components/PlAgGridColumnManager/useGridColumns.js +14 -0
  47. package/dist/components/PlAgGridColumnManager/useGridColumns.js.map +1 -0
  48. package/dist/components/PlAnnotations/components/FilterSidebar.js.map +1 -1
  49. package/dist/components/PlAnnotations/components/FilterSidebar.style.js.map +1 -1
  50. package/dist/components/PlAnnotations/components/FilterSidebar.vue.d.ts.map +1 -1
  51. package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js +7 -4
  52. package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js.map +1 -1
  53. package/dist/components/PlTableFilters/PlTableFiltersV2.js.map +1 -1
  54. package/dist/components/PlTableFilters/PlTableFiltersV2.style.js +5 -1
  55. package/dist/components/PlTableFilters/PlTableFiltersV2.style.js.map +1 -1
  56. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.css +1 -1
  57. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.d.ts +7 -9
  58. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.d.ts.map +1 -1
  59. package/dist/components/PlTableFilters/PlTableFiltersV2.vue2.js +72 -47
  60. package/dist/components/PlTableFilters/PlTableFiltersV2.vue2.js.map +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +2 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/lib/util/helpers/dist/functions.js.map +1 -1
  65. package/dist/lib/util/helpers/dist/objects.js +4 -1
  66. package/dist/lib/util/helpers/dist/objects.js.map +1 -1
  67. package/package.json +8 -7
  68. package/src/components/PlAdvancedFilter/FilterEditor.vue +99 -55
  69. package/src/components/PlAdvancedFilter/PlAdvancedFilter.vue +163 -95
  70. package/src/components/PlAdvancedFilter/types.ts +6 -1
  71. package/src/components/PlAgDataTable/PlAgDataTableV2.vue +26 -7
  72. package/src/components/PlAgDataTable/compositions/useFilterableColumns.ts +4 -4
  73. package/src/components/PlAgDataTable/sources/table-source-v2.ts +231 -131
  74. package/src/components/PlAgDataTable/sources/table-state-v2.ts +249 -70
  75. package/src/components/PlAgGridColumnManager/PlAgGridColumnManager.vue +17 -35
  76. package/src/components/PlAgGridColumnManager/useFilteredItems.ts +9 -11
  77. package/src/components/PlAgGridColumnManager/useGridColumns.ts +26 -0
  78. package/src/components/PlAnnotations/components/FilterSidebar.vue +3 -2
  79. package/src/components/PlTableFilters/PlTableFiltersV2.vue +76 -26
  80. package/src/index.ts +4 -0
@@ -12,18 +12,19 @@ import {
12
12
  type PlDataTableStateV2,
13
13
  type PlDataTableStateV2CacheEntry,
14
14
  type PlDataTableStateV2Normalized,
15
- type PObjectId,
16
15
  type PTableParamsV2,
17
16
  type PTableSorting,
17
+ type PlDataTableFilterSpecLeaf,
18
+ type PlDataTableFilterMeta,
18
19
  type PlDataTableFilters,
19
20
  distillFilterSpec,
20
21
  PlDataTableFiltersWithMeta,
21
22
  getPTableColumnId,
22
23
  CanonicalizedJson,
23
24
  } from "@platforma-sdk/model";
24
- import { computed, ref, watch, type Ref, type WritableComputedRef } from "vue";
25
+ import { computed, type Ref, type WritableComputedRef } from "vue";
25
26
  import type { PlDataTableSettingsV2 } from "../types";
26
- import { isJsonEqual, randomInt } from "@milaboratories/helpers";
27
+ import { isJsonEqual, randomInt, getField, Nil } from "@milaboratories/helpers";
27
28
  import { computedCached } from "@milaboratories/uikit";
28
29
  import { isStringValueType, isNumericValueType } from "../../PlAdvancedFilter/utils";
29
30
  import { debounce, isNil } from "es-toolkit";
@@ -32,11 +33,15 @@ export function useTableState(
32
33
  tableStateDenormalized: Ref<PlDataTableStateV2>,
33
34
  settings: Ref<PlDataTableSettingsV2>,
34
35
  columns: Ref<PTableColumnSpec[]>,
36
+ defaultFilters: Ref<Nil | PlDataTableFilters>,
35
37
  ): {
36
38
  gridState: WritableComputedRef<PlDataTableGridStateCore>;
37
39
  sheetsState: WritableComputedRef<PlDataTableSheetState[]>;
38
- filtersState: Ref<PlDataTableFiltersWithMeta>;
40
+
39
41
  searchString: WritableComputedRef<string>;
42
+ filtersState: Ref<PlDataTableFiltersWithMeta>;
43
+ defaultFiltersState: Ref<null | PlDataTableFiltersWithMeta>;
44
+ resetDefaultFilters: () => void;
40
45
  } {
41
46
  const tableStateNormalized = computedCached<PlDataTableStateV2Normalized>({
42
47
  get: () => upgradePlDataTableStateV2(tableStateDenormalized.value),
@@ -113,18 +118,11 @@ export function useTableState(
113
118
  },
114
119
  });
115
120
 
121
+ // --- User filters (editable by user) ---
116
122
  const filtersState = computed<PlDataTableFiltersWithMeta>({
117
123
  get: () => {
118
124
  const raw = tableState.value.filtersState;
119
- const isCorrect =
120
- raw &&
121
- (raw.type === "and" || raw.type === "or") &&
122
- "filters" in raw &&
123
- Array.isArray(raw.filters);
124
-
125
- return isCorrect
126
- ? (raw satisfies PlDataTableFiltersWithMeta)
127
- : { id: randomInt(), type: "and" as const, isExpanded: true, filters: [] };
125
+ return isNil(raw) ? getEmptyGroupWithMeta() : normalizeFiltersState(raw);
128
126
  },
129
127
  set: (filtersState: PlDataTableFiltersWithMeta) => {
130
128
  const oldState = tableState.value;
@@ -136,12 +134,35 @@ export function useTableState(
136
134
  }
137
135
  },
138
136
  });
139
- const filtersStateDeepReactive = ref(filtersState);
140
- watch(
141
- () => filtersStateDeepReactive.value,
142
- (newValue) => (filtersState.value = newValue),
143
- { deep: true },
144
- );
137
+
138
+ // --- Default filters (from model, separate list) ---
139
+ const defaultFiltersState = computed<null | PlDataTableFiltersWithMeta>({
140
+ get: () => {
141
+ const raw = tableState.value.defaultFiltersState;
142
+ if (!isNil(raw)) {
143
+ return normalizeFiltersState(raw);
144
+ }
145
+ if (!isNil(defaultFilters.value)) {
146
+ return annotateFiltersWithIds(normalizeFiltersState(defaultFilters.value));
147
+ }
148
+ return null;
149
+ },
150
+ set: (defaultFiltersState: null | PlDataTableFiltersWithMeta) => {
151
+ const oldState = tableState.value;
152
+ if (oldState.sourceId) {
153
+ tableState.value = {
154
+ ...oldState,
155
+ defaultFiltersState,
156
+ };
157
+ }
158
+ },
159
+ });
160
+
161
+ function resetDefaultFilters(): void {
162
+ defaultFiltersState.value = isNil(defaultFilters.value)
163
+ ? null
164
+ : annotateFiltersWithIds(normalizeFiltersState(defaultFilters.value));
165
+ }
145
166
 
146
167
  const searchString = computed<string>({
147
168
  get: () => tableState.value.searchString ?? "",
@@ -156,9 +177,18 @@ export function useTableState(
156
177
  },
157
178
  });
158
179
 
159
- return { gridState, sheetsState, filtersState: filtersStateDeepReactive, searchString };
180
+ return {
181
+ gridState,
182
+ sheetsState,
183
+ searchString,
184
+ filtersState,
185
+ defaultFiltersState,
186
+ resetDefaultFilters,
187
+ };
160
188
  }
161
189
 
190
+ // --- Types ---
191
+
162
192
  type PlDataTableStateV2CacheEntryNullable =
163
193
  | PlDataTableStateV2CacheEntry
164
194
  | {
@@ -166,51 +196,138 @@ type PlDataTableStateV2CacheEntryNullable =
166
196
  gridState: Record<string, never>;
167
197
  sheetsState: [];
168
198
  filtersState: null;
199
+ defaultFiltersState: null;
169
200
  searchString?: string;
170
201
  };
171
202
 
172
- function makeDefaultState(): PlDataTableStateV2CacheEntryNullable {
203
+ type FilterNode = FilterSpec<PlDataTableFilterSpecLeaf>;
204
+ type AnnotatedFilterSpec = FilterSpec<PlDataTableFilterSpecLeaf, PlDataTableFilterMeta>;
205
+
206
+ // --- Core ---
207
+
208
+ function createPTableParams(
209
+ state: PlDataTableStateV2CacheEntry,
210
+ filterableColumns: PTableColumnSpec[],
211
+ ): PTableParamsV2 {
212
+ // User filters: sheets + user filter state + search
213
+ const searchNode = createSearchFilterNode(filterableColumns, state.searchString);
214
+ const unsuppressedUserFilters = isNil(state.filtersState)
215
+ ? null
216
+ : stripSuppressedFilters(state.filtersState);
217
+ const userParts = [
218
+ ...convertPartitionFiltersToFilterSpec(state.sheetsState),
219
+ ...(isNil(unsuppressedUserFilters) ? [] : [unsuppressedUserFilters]),
220
+ ...(isNil(searchNode) ? [] : [searchNode]),
221
+ ];
222
+ const filters: null | PlDataTableFilters = distillFilterSpec(
223
+ userParts.length === 0
224
+ ? null
225
+ : userParts.length === 1
226
+ ? userParts[0]
227
+ : { type: "and", filters: userParts },
228
+ );
229
+ const unsuppressedDefaultFilters = isNil(state.defaultFiltersState)
230
+ ? null
231
+ : stripSuppressedFilters(state.defaultFiltersState);
232
+ const defaultFilters: null | PlDataTableFilters = isNil(unsuppressedDefaultFilters)
233
+ ? null
234
+ : // If all filters are suppressed, we should pass an empty filter group instead of null to prevent fallback to defaults in the model
235
+ (distillFilterSpec(unsuppressedDefaultFilters) ?? getEmptyGroup());
236
+
173
237
  return {
174
- sourceId: null,
175
- gridState: {},
176
- sheetsState: [],
177
- filtersState: null,
238
+ sourceId: state.sourceId,
239
+ hiddenColIds: getHiddenColIds(state.gridState.columnVisibility),
240
+ sorting: convertAgSortingToPTableSorting(state.gridState.sort),
241
+ filters,
242
+ defaultFilters,
178
243
  };
179
244
  }
180
245
 
181
- function getHiddenColIds(state: PlDataTableGridStateCore["columnVisibility"]): PObjectId[] | null {
182
- return (
183
- state?.hiddenColIds?.map(parseJson).reduce((acc, c) => {
184
- if (c.source.type === "column") {
185
- acc.push(c.source.id);
186
- }
187
- return acc;
188
- }, [] as PObjectId[]) ?? null
189
- );
246
+ /**
247
+ * Normalizes raw filter state into a valid root filter structure.
248
+ * Valid structure is Root(Group, Group, ...) double nesting required:
249
+ * root is and/or group, each child is also and/or group containing leaf filters.
250
+ * - null/undefined/invalid → empty root
251
+ * - Leaf node → Root(Group(leaf))
252
+ * - Group with leaf children → Root(Group(leaves...))
253
+ * - Group with group children → as-is
254
+ */
255
+ function normalizeFiltersState(raw: FilterNode) {
256
+ // Leaf node → wrap in double nesting: Root(Group(leaf))
257
+ if (raw.type !== "and" && raw.type !== "or" && raw.type !== "not") {
258
+ if ("type" in raw && !isNil(raw.type)) {
259
+ return {
260
+ id: randomInt(),
261
+ type: "and" as const,
262
+ isExpanded: true,
263
+ filters: [
264
+ {
265
+ id: randomInt(),
266
+ type: "and" as const,
267
+ isExpanded: true,
268
+ filters: [raw as AnnotatedFilterSpec],
269
+ } as AnnotatedFilterSpec,
270
+ ],
271
+ };
272
+ }
273
+ return getEmptyGroupWithMeta();
274
+ }
275
+
276
+ // Already a group — ensure children are also groups (double nesting)
277
+ if ((raw.type === "and" || raw.type === "or") && "filters" in raw && Array.isArray(raw.filters)) {
278
+ const allChildrenAreGroups = raw.filters.every(
279
+ (f: FilterNode) => f.type === "and" || f.type === "or" || f.type === "not",
280
+ );
281
+ if (allChildrenAreGroups) {
282
+ return raw as PlDataTableFiltersWithMeta;
283
+ }
284
+ // Children are leaves — wrap them in a single group
285
+ return {
286
+ id: randomInt(),
287
+ type: raw.type as "and" | "or",
288
+ isExpanded: true,
289
+ filters: [
290
+ {
291
+ id: randomInt(),
292
+ type: raw.type as "and" | "or",
293
+ isExpanded: true,
294
+ filters: raw.filters as AnnotatedFilterSpec[],
295
+ } as AnnotatedFilterSpec,
296
+ ],
297
+ };
298
+ }
299
+
300
+ return getEmptyGroupWithMeta();
190
301
  }
191
302
 
192
- function convertPartitionFiltersToFilterSpec(
193
- sheetsState: PlDataTableSheetState[],
194
- ): FilterSpec<FilterSpecLeaf<CanonicalizedJson<PTableColumnId>>>[] {
195
- return sheetsState.map((s) => {
196
- const column = canonicalizeJson<PTableColumnId>({ type: "axis", id: s.axisId });
197
- return typeof s.value === "number"
198
- ? { type: "equal" as const, column, x: s.value }
199
- : { type: "patternEquals" as const, column, value: s.value };
200
- });
303
+ function getEmptyGroup(): PlDataTableFilters {
304
+ return {
305
+ type: "and" as const,
306
+ filters: [],
307
+ };
308
+ }
309
+ function getEmptyGroupWithMeta(): PlDataTableFiltersWithMeta {
310
+ return {
311
+ ...getEmptyGroup(),
312
+ id: randomInt(),
313
+ isExpanded: true,
314
+ } as PlDataTableFiltersWithMeta;
201
315
  }
202
316
 
203
- function convertAgSortingToPTableSorting(state: PlDataTableGridStateCore["sort"]): PTableSorting[] {
204
- return (
205
- state?.sortModel.map((item) => {
206
- const { spec: _, ...column } = parseJson(item.colId).labeled;
207
- return {
208
- column,
209
- ascending: item.sort === "asc",
210
- naAndAbsentAreLeastValues: item.sort === "asc",
211
- };
212
- }) ?? []
213
- );
317
+ /**
318
+ * Recursively removes nodes where isSuppressed === true from a PlDataTableFiltersWithMeta tree.
319
+ */
320
+ function stripSuppressedFilters(node: PlDataTableFiltersWithMeta): PlDataTableFiltersWithMeta {
321
+ return {
322
+ ...node,
323
+ filters: node.filters
324
+ .filter((child) => !("isSuppressed" in child && child.isSuppressed === true))
325
+ .map((child) =>
326
+ "filters" in child && (child.type === "and" || child.type === "or")
327
+ ? stripSuppressedFilters(child as PlDataTableFiltersWithMeta)
328
+ : child,
329
+ ),
330
+ };
214
331
  }
215
332
 
216
333
  function createSearchFilterNode(
@@ -242,24 +359,86 @@ function createSearchFilterNode(
242
359
  return { type: "or", filters: parts };
243
360
  }
244
361
 
245
- function createPTableParams(
246
- state: PlDataTableStateV2CacheEntry,
247
- filterableColumns: PTableColumnSpec[],
248
- ): PTableParamsV2 {
249
- const searchNode = createSearchFilterNode(filterableColumns, state.searchString);
250
- const parts = [
251
- ...convertPartitionFiltersToFilterSpec(state.sheetsState),
252
- ...(state.filtersState ? [state.filtersState] : []),
253
- ...(searchNode ? [searchNode] : []),
254
- ];
255
- const filters: null | PlDataTableFilters = distillFilterSpec(
256
- parts.length === 0 ? null : parts.length === 1 ? parts[0] : { type: "and", filters: parts },
362
+ // --- Helpers ---
363
+
364
+ /**
365
+ * Recursively ensures every node in a filter tree has an `id` field.
366
+ * Does not set `source` meta — defaults are now a separate list.
367
+ */
368
+ function annotateFiltersWithIds(
369
+ filters: PlDataTableFilters | PlDataTableFiltersWithMeta,
370
+ ): PlDataTableFiltersWithMeta {
371
+ return annotateNodeWithIds(filters) as PlDataTableFiltersWithMeta;
372
+ }
373
+
374
+ function annotateNodeWithIds(
375
+ node: FilterNode | PlDataTableFilters | PlDataTableFiltersWithMeta,
376
+ ): AnnotatedFilterSpec {
377
+ switch (node.type) {
378
+ case "and":
379
+ return {
380
+ id: getField(node, "id") ?? randomInt(),
381
+ isExpanded: getField(node, "isExpanded") ?? true,
382
+ type: "and" as const,
383
+ filters: node.filters.map((child) => annotateNodeWithIds(child)),
384
+ };
385
+ case "or":
386
+ return {
387
+ id: getField(node, "id") ?? randomInt(),
388
+ isExpanded: getField(node, "isExpanded") ?? true,
389
+ type: "or" as const,
390
+ filters: node.filters.map((child) => annotateNodeWithIds(child)),
391
+ };
392
+ case "not":
393
+ return {
394
+ id: randomInt(),
395
+ isExpanded: true,
396
+ type: "not" as const,
397
+ filter: annotateNodeWithIds(node.filter),
398
+ };
399
+ default:
400
+ return { ...node, id: getField(node, "id") ?? randomInt() } as AnnotatedFilterSpec;
401
+ }
402
+ }
403
+
404
+ // --- Utilities ---
405
+
406
+ function convertPartitionFiltersToFilterSpec(
407
+ sheetsState: PlDataTableSheetState[],
408
+ ): FilterSpec<FilterSpecLeaf<CanonicalizedJson<PTableColumnId>>>[] {
409
+ return sheetsState.map((s) => {
410
+ const column = canonicalizeJson<PTableColumnId>({ type: "axis", id: s.axisId });
411
+ return typeof s.value === "number"
412
+ ? { type: "equal" as const, column, x: s.value }
413
+ : { type: "patternEquals" as const, column, value: s.value };
414
+ });
415
+ }
416
+
417
+ function convertAgSortingToPTableSorting(state: PlDataTableGridStateCore["sort"]): PTableSorting[] {
418
+ return (
419
+ state?.sortModel.map((item) => {
420
+ const { spec: _, ...column } = parseJson(item.colId).labeled;
421
+ return {
422
+ column,
423
+ ascending: item.sort === "asc",
424
+ naAndAbsentAreLeastValues: item.sort === "asc",
425
+ };
426
+ }) ?? []
257
427
  );
428
+ }
429
+
430
+ function getHiddenColIds(
431
+ state: PlDataTableGridStateCore["columnVisibility"],
432
+ ): PTableColumnId[] | null {
433
+ return state?.hiddenColIds?.map((json) => getPTableColumnId(parseJson(json).source)) ?? null;
434
+ }
258
435
 
436
+ function makeDefaultState(): PlDataTableStateV2CacheEntryNullable {
259
437
  return {
260
- sourceId: state.sourceId,
261
- hiddenColIds: getHiddenColIds(state.gridState.columnVisibility),
262
- filters,
263
- sorting: convertAgSortingToPTableSorting(state.gridState.sort),
438
+ sourceId: null,
439
+ gridState: {},
440
+ sheetsState: [],
441
+ filtersState: null,
442
+ defaultFiltersState: null,
264
443
  };
265
444
  }
@@ -6,10 +6,11 @@ import {
6
6
  PlSlideModal,
7
7
  usePlBlockPageTitleTeleportTarget,
8
8
  } from "@milaboratories/uikit";
9
- import { type Column, type DisplayedColumnsChangedEvent, type GridApi } from "ag-grid-enterprise";
10
- import { computed, ref, toRefs, watch } from "vue";
9
+ import { type GridApi } from "ag-grid-enterprise";
10
+ import { computed, ref } from "vue";
11
11
  import { PlAgDataTableRowNumberColId } from "../PlAgDataTable/sources/row-number";
12
12
  import { useFilteredItems } from "./useFilteredItems";
13
+ import { useGridColumns } from "./useGridColumns";
13
14
 
14
15
  const props = defineProps<{
15
16
  /**
@@ -25,44 +26,25 @@ const props = defineProps<{
25
26
  width?: string;
26
27
  }>();
27
28
 
28
- const { api: gridApi } = toRefs(props);
29
-
30
- const columns = ref<Column[]>([]);
31
- watch(
32
- () => gridApi.value,
33
- (gridApi) => {
34
- if (gridApi.isDestroyed()) return;
35
-
36
- gridApi.addEventListener("displayedColumnsChanged", (event: DisplayedColumnsChangedEvent) => {
37
- columns.value = event.api.getAllGridColumns();
38
- });
29
+ const query = ref("");
30
+ const slideModal = ref(false);
31
+ const teleportTarget = usePlBlockPageTitleTeleportTarget("PlAgGridColumnManager");
39
32
 
40
- columns.value = gridApi.getAllGridColumns();
41
- if (columns.value.length > 0) {
42
- gridApi.moveColumns(columns.value, 0);
43
- }
44
- },
45
- { immediate: true },
46
- );
33
+ const columns = useGridColumns(props);
47
34
 
48
35
  const items = computed(() => {
49
- return columns.value.map((col) => ({
36
+ return columns.value.map((col, i) => ({
50
37
  column: col,
51
38
  id: col.getId(),
52
- label: col.getColDef().headerName!,
39
+ label: col.getColDef().headerName ?? `Unnamed Column (${i + 1})`,
53
40
  }));
54
41
  });
55
42
 
56
- const query = ref("");
57
-
58
- const slideModal = ref(false);
59
- const teleportTarget = usePlBlockPageTitleTeleportTarget("PlAgGridColumnManager");
60
-
61
- const { filteredItems, segments } = useFilteredItems(() => ({
62
- items: items.value,
63
- query: query.value,
43
+ const { filteredItems, segments } = useFilteredItems({
44
+ items,
45
+ query,
64
46
  getStrings: (item) => [item.label],
65
- }));
47
+ });
66
48
  </script>
67
49
 
68
50
  <template>
@@ -79,17 +61,17 @@ const { filteredItems, segments } = useFilteredItems(() => ({
79
61
  :is-draggable="(item) => !item.column.getColDef().lockPosition"
80
62
  :on-sort="
81
63
  (fromIndex, toIndex) => {
82
- if (!gridApi.isDestroyed()) {
64
+ if (!props.api.isDestroyed()) {
83
65
  const columnToMove = columns[fromIndex];
84
- gridApi.moveColumns([columnToMove], toIndex);
66
+ props.api.moveColumns([columnToMove], toIndex);
85
67
  }
86
68
  return true; // Let PlElementList handle the visual update
87
69
  }
88
70
  "
89
71
  :on-toggle="
90
72
  (item) => {
91
- if (!gridApi.isDestroyed()) {
92
- gridApi.setColumnsVisible([item.column], !item.column.isVisible());
73
+ if (!props.api.isDestroyed()) {
74
+ props.api.setColumnsVisible([item.column], !item.column.isVisible());
93
75
  }
94
76
  }
95
77
  "
@@ -1,25 +1,23 @@
1
- import { computed, type MaybeRefOrGetter, toValue } from "vue";
1
+ import { Ref, computed, toValue } from "vue";
2
2
 
3
- export function useFilteredItems<T>(
4
- props: MaybeRefOrGetter<{
5
- items: T[];
6
- query: string;
7
- getStrings: (item: T) => Iterable<string>;
8
- }>,
9
- ) {
3
+ export function useFilteredItems<T>(props: {
4
+ items: Ref<T[]>;
5
+ query: Ref<string>;
6
+ getStrings: (item: T) => Iterable<string>;
7
+ }) {
10
8
  const result = computed(() => {
11
9
  const { items, query, getStrings } = toValue(props);
12
10
  const filteredItems: T[] = [];
13
11
  const segments = new Map<string, StringSegment[]>();
14
- for (const item of items) {
12
+ for (const item of items.value) {
15
13
  let kept = false;
16
14
  for (const string of getStrings(item)) {
17
15
  let stringSegments = segments.get(string);
18
16
  if (!stringSegments) {
19
- stringSegments = matchSubstrings(string, query);
17
+ stringSegments = matchSubstrings(string, query.value);
20
18
  segments.set(string, stringSegments);
21
19
  }
22
- if (!kept && (!query || stringSegments.some(({ match }) => match))) {
20
+ if (!kept && (!query.value || stringSegments.some(({ match }) => match))) {
23
21
  filteredItems.push(item);
24
22
  kept = true;
25
23
  }
@@ -0,0 +1,26 @@
1
+ import { Column, GridApi } from "ag-grid-enterprise";
2
+ import { ref, watch } from "vue";
3
+
4
+ export function useGridColumns(props: { api: GridApi }) {
5
+ const columns = ref<Column[]>([]);
6
+ const syncColumns = () => {
7
+ columns.value = props.api.getAllGridColumns();
8
+ };
9
+ watch(
10
+ () => props.api,
11
+ (gridApi, _, onCleanup) => {
12
+ if (gridApi.isDestroyed()) return;
13
+
14
+ gridApi.addEventListener("displayedColumnsChanged", syncColumns);
15
+ onCleanup(() => gridApi.removeEventListener("displayedColumnsChanged", syncColumns));
16
+
17
+ columns.value = gridApi.getAllGridColumns();
18
+ if (columns.value.length > 0) {
19
+ gridApi.moveColumns(columns.value, 0);
20
+ }
21
+ },
22
+ { immediate: true },
23
+ );
24
+
25
+ return columns;
26
+ }
@@ -116,13 +116,14 @@ const supportedFilters = [
116
116
  </template>
117
117
  <template #body-content>
118
118
  <PlAdvancedFilterComponent
119
- v-model:filters="step.filter as PlAdvancedFilter"
119
+ :filters="step.filter as PlAdvancedFilter"
120
120
  :class="[$style.root, { [$commonStyle.disabled]: step.label.length === 0 }]"
121
- :items="props.columns"
121
+ :options="props.columns"
122
122
  :supported-filters="supportedFilters"
123
123
  :get-suggest-options="props.getSuggestOptions"
124
124
  :enable-dnd="false"
125
125
  :enable-add-group-button="true"
126
+ @update-filters="(v) => (step = { ...step, filter: v as typeof step.filter })"
126
127
  >
127
128
  <template #add-group-buttons>
128
129
  <div :class="$style.actions">