@platforma-sdk/ui-vue 1.84.17 → 1.84.19

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.
@@ -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.17 build /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.84.19 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
  4 export function useGridColumns(props: { api: GridApi }) {
34
34
     ~~~~~~~~~~~~~~
35
35
 
36
- 
36
+ 
37
37
  rendering chunks...
38
38
 
39
39
  [unplugin:dts] Start generate declaration files...
40
- [unplugin:dts] Declaration files built in 4662ms.
40
+ [unplugin:dts] Declaration files built in 4768ms.
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
@@ -146,7 +146,7 @@ dist/components/PlAgDataTable/PlAgDataTableV2.js
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: 14.05 kB
148
148
  dist/components/PlAgDataTable/PlAgOverlayNoRows.js 0.36 kB │ gzip: 0.21 kB │ map: 0.74 kB
149
- dist/components/PlTableFilters/PlTableFiltersV2.js 0.36 kB │ gzip: 0.25 kB │ map: 7.22 kB
149
+ dist/components/PlTableFilters/PlTableFiltersV2.js 0.36 kB │ gzip: 0.25 kB │ map: 7.17 kB
150
150
  dist/components/PlAgCellStatusTag/PlAgCellStatusTag.js 0.36 kB │ gzip: 0.20 kB │ map: 0.74 kB
151
151
  dist/components/PlBtnExportArchive/PlBtnExportArchive.js 0.36 kB │ gzip: 0.24 kB │ map: 7.47 kB
152
152
  dist/components/PlAgDataTable/PlAgDataTableSheets.js 0.37 kB │ gzip: 0.25 kB │ map: 3.73 kB
@@ -189,6 +189,7 @@ dist/AgGridVue/selection.js
189
189
  dist/components/PlAdvancedFilter/OperandButton.vue_vue_type_script_setup_true_lang.js 1.01 kB │ gzip: 0.60 kB │ map: 1.84 kB
190
190
  dist/components/PlAgChartStackedBarCell/PlAgChartStackedBarCell.vue_vue_type_script_setup_true_lang.js 1.04 kB │ gzip: 0.53 kB │ map: 1.57 kB
191
191
  dist/components/NotFound.vue_vue_type_script_setup_true_lang.js 1.04 kB │ gzip: 0.62 kB │ map: 1.09 kB
192
+ dist/components/PlTableFilters/suggest-source.js 1.14 kB │ gzip: 0.64 kB │ map: 3.26 kB
192
193
  dist/plugins/Monetization/RunStatus.vue_vue_type_script_setup_true_lang.js 1.16 kB │ gzip: 0.62 kB │ map: 3.04 kB
193
194
  dist/components/PlAgDataTable/sources/value-rendering.js 1.16 kB │ gzip: 0.56 kB │ map: 3.38 kB
194
195
  dist/components/PlAgGridColumnManager/useFilteredItems.js 1.18 kB │ gzip: 0.61 kB │ map: 3.48 kB
@@ -236,7 +237,7 @@ dist/AgGridVue/useAgGridOptions.js
236
237
  dist/components/PlAnnotations/components/FilterSidebar.vue_vue_type_script_setup_true_lang.js 4.24 kB │ gzip: 1.62 kB │ map: 7.80 kB
237
238
  dist/components/PlAnnotations/components/AnnotationsSidebar.vue_vue_type_script_setup_true_lang.js 4.29 kB │ gzip: 1.67 kB │ map: 7.04 kB
238
239
  dist/index.js 4.50 kB │ gzip: 1.36 kB │ map: 0.25 kB
239
- dist/components/PlTableFilters/PlTableFiltersV2.vue_vue_type_script_setup_true_lang.js 5.21 kB │ gzip: 2.08 kB │ map: 10.01 kB
240
+ dist/components/PlTableFilters/PlTableFiltersV2.vue_vue_type_script_setup_true_lang.js 5.19 kB │ gzip: 2.07 kB │ map: 10.03 kB
240
241
  dist/components/PlBtnExportArchive/PlBtnExportArchive.vue_vue_type_script_setup_true_lang.js 5.40 kB │ gzip: 2.20 kB │ map: 10.68 kB
241
242
  dist/components/PlAgDataTable/sources/table-state-v2.js 5.71 kB │ gzip: 1.83 kB │ map: 20.20 kB
242
243
  dist/internal/createAppV3.js 6.19 kB │ gzip: 2.33 kB │ map: 21.40 kB
@@ -246,12 +247,12 @@ dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lan
246
247
  dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.76 kB │ gzip: 4.02 kB │ map: 32.97 kB
247
248
 
248
249
  [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:
249
- - unplugin-dts (25%)
250
- - sourcemaps (23%)
251
- - vite:css-post (16%)
252
- - vite:vue (13%)
253
- - vite:css (8%)
250
+ - sourcemaps (44%)
251
+ - unplugin-dts (21%)
252
+ - vite:css-post (10%)
253
+ - vite:vue (8%)
254
+ - vite:css (7%)
254
255
  See https://rolldown.rs/options/checks#plugintimings for more details.
255
256
  
256
- ✓ built in 5.54s
257
+ ✓ built in 5.55s
257
258
  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.17 formatter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.84.19 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 1988ms on 132 files using 16 threads.
11
+ Finished in 1708ms on 134 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.17 linter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.84.19 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 27ms on 115 files with 108 rules using 16 threads.
9
+ Finished in 28ms on 117 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.17 types:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.84.19 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,14 @@
1
1
  # @platforma-sdk/ui-vue
2
2
 
3
+ ## 1.84.19
4
+
5
+ ### Patch Changes
6
+
7
+ - fc41fe4: Table filters: suggest values for axis columns, merged across every table column carrying the axis, instead of failing with "Data loading error"; offer "In List" / "Not In List" predicates. New `getUniqueAxisValuesWithLabels` in `@platforma-sdk/model`.
8
+ - Updated dependencies [fc41fe4]
9
+ - @platforma-sdk/model@1.84.19
10
+ - @milaboratories/uikit@2.15.40
11
+
3
12
  ## 1.84.17
4
13
 
5
14
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"PlTableFiltersV2.js","names":[],"sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type {\n PTableColumnSpec,\n PlDataTableFiltersWithMeta,\n PFrameHandle,\n PTableColumnId,\n PColumnSpec,\n} from \"@platforma-sdk/model\";\nimport {\n Annotation,\n Domain,\n readAnnotation,\n readDomain,\n getUniqueSourceValuesWithLabels,\n extractPObjectId,\n getPTableColumnId,\n deriveDistinctLabels,\n} from \"@platforma-sdk/model\";\nimport { computed, ref } from \"vue\";\nimport { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport {\n PlAdvancedFilter,\n PlAdvancedFilterComponent,\n PlAdvancedFilterSupportedFilters,\n type PlAdvancedFilterItem,\n} from \"../PlAdvancedFilter\";\nimport type { PlAdvancedFilterColumnId } from \"../PlAdvancedFilter/types\";\nimport type { Nil } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n columns: PTableColumnSpec[];\n pframeHandle: Nil | PFrameHandle;\n filters: PlDataTableFiltersWithMeta;\n defaultFilters: Nil | PlDataTableFiltersWithMeta;\n onUpdateFilters: (value: PlDataTableFiltersWithMeta) => void;\n onResetDefaultFilters?: () => void;\n onUpdateDefaultFilters?: (value: PlDataTableFiltersWithMeta) => void;\n}>();\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlTableFiltersV2\");\nconst showManager = ref(false);\nconst hasFilters = computed(() => props.filters.filters.length > 0);\nconst hasDefaultFilters = computed(\n () => !isNil(props.defaultFilters) && props.defaultFilters.filters.length > 0,\n);\nconst filters = computed<PlDataTableFiltersWithMeta>(() => {\n if (isNil(props.defaultFilters) || props.defaultFilters?.filters.length === 0) {\n return props.filters;\n }\n\n return {\n ...props.filters,\n filters: [...(props.defaultFilters?.filters ?? []), ...props.filters.filters],\n };\n});\nconst onUpdateFilters = (_value: PlAdvancedFilter) => {\n const value = _value as PlDataTableFiltersWithMeta;\n\n if (isNil(props.defaultFilters)) {\n return props.onUpdateFilters(value);\n }\n\n const [defaults, ...rest] = value.filters;\n props.onUpdateFilters({ ...value, filters: rest });\n props.onUpdateDefaultFilters?.({ ...props.defaultFilters, filters: [defaults] });\n};\n\nconst options = computed<PlAdvancedFilterItem[]>(() => {\n const columnEntries: { idx: number; spec: PColumnSpec }[] = [];\n props.columns.forEach((col, idx) => {\n if (col.type === \"column\") columnEntries.push({ idx, spec: col.spec });\n });\n const distinctLabelByIdx = new Map<number, string>();\n deriveDistinctLabels(\n columnEntries.map((e) => e.spec),\n {},\n ).forEach((label, i) => distinctLabelByIdx.set(columnEntries[i].idx, label));\n\n return props.columns.map((col, idx) => {\n const id = getPTableColumnId(col);\n const label =\n distinctLabelByIdx.get(idx)?.trim() ||\n readAnnotation(col.spec, Annotation.Label)?.trim() ||\n `Unlabeled ${col.type} ${idx}`;\n const alphabet =\n readDomain(col.spec, Domain.Alphabet) ?? readAnnotation(col.spec, Annotation.Alphabet);\n return {\n id,\n label,\n spec: col.spec,\n alphabet: alphabet ?? undefined,\n };\n });\n});\n\n// Supported filters (same set as FilterSidebar)\nconst supportedFilters = [\n \"isNA\",\n \"isNotNA\",\n \"greaterThan\",\n \"greaterThanOrEqual\",\n \"lessThan\",\n \"lessThanOrEqual\",\n \"patternEquals\",\n \"patternNotEquals\",\n \"patternContainSubsequence\",\n \"patternNotContainSubsequence\",\n \"patternMatchesRegularExpression\",\n \"patternFuzzyContainSubsequence\",\n \"equal\",\n \"notEqual\",\n] as (typeof PlAdvancedFilterSupportedFilters)[number][];\n\n// getSuggestOptions - provide discrete values from column annotations\nfunction handleSuggestOptions(params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchStr: string;\n searchType: \"value\" | \"label\";\n}) {\n if (isNil(props.pframeHandle)) {\n console.warn(\"PFrame handle is not available, cannot fetch suggest options\");\n return [];\n }\n\n const tableColumnId = params.columnId as PTableColumnId;\n if (\n typeof tableColumnId !== \"object\" ||\n tableColumnId === null ||\n tableColumnId.type !== \"column\"\n ) {\n throw new Error(\"ColumnId should be of type 'column' for suggest options\");\n }\n\n return getUniqueSourceValuesWithLabels(props.pframeHandle, {\n columnId: extractPObjectId(tableColumnId.id),\n axisIdx: params.axisIdx,\n limit: 100,\n searchQuery: params.searchType === \"label\" ? params.searchStr : undefined,\n searchQueryValue: params.searchType === \"value\" ? params.searchStr : undefined,\n }).then((v) => v.values);\n}\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget\" :to=\"teleportTarget\">\n <PlBtnGhost\n data-testid=\"pl-table-filters\"\n :icon=\"hasFilters || hasDefaultFilters ? 'filter-on' : 'filter'\"\n @click.stop=\"showManager = true\"\n >\n Filters\n </PlBtnGhost>\n </Teleport>\n\n <PlSlideModal\n v-model=\"showManager\"\n data-testid=\"pl-table-filters-panel\"\n :close-on-outside-click=\"false\"\n >\n <template #title>Manage Filters</template>\n\n <div data-testid=\"pl-table-filters-content\" :class=\"$style.root\">\n <PlAdvancedFilterComponent\n :filters=\"filters as PlAdvancedFilter\"\n :options=\"options\"\n :supported-filters=\"supportedFilters\"\n :get-suggest-options=\"handleSuggestOptions\"\n :is-pinned=\"(_, index) => hasDefaultFilters && index === 0\"\n :is-removable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :is-draggable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :enable-dnd=\"false\"\n :enable-toggling=\"true\"\n :enable-add-group-button=\"true\"\n @update-filters=\"onUpdateFilters\"\n >\n <template #group-title=\"{ index }\">\n <div\n v-if=\"hasDefaultFilters && index === 0\"\n data-testid=\"pl-table-filters-default-group-title\"\n :class=\"$style.defaultGroupTitle\"\n >\n Default Group\n <PlBtnGhost\n v-if=\"isFunction(props.onResetDefaultFilters)\"\n data-testid=\"pl-table-filters-reset-default\"\n icon=\"restart\"\n :class=\"$style.restartBtn\"\n @click.stop=\"props.onResetDefaultFilters()\"\n />\n </div>\n <div v-else data-testid=\"pl-table-filters-custom-group-title\">Custom Group</div>\n </template>\n </PlAdvancedFilterComponent>\n </div>\n </PlSlideModal>\n</template>\n\n<style module>\n.root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.defaultGroupTitle {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.restartBtn {\n width: 24px;\n height: 24px;\n padding: 4px;\n --button-width: 24px;\n --btn-min-width: 24px;\n --button-height: 24px;\n --btn-min-height: 24x;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"PlTableFiltersV2.js","names":[],"sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type {\n PTableColumnSpec,\n PlDataTableFiltersWithMeta,\n PFrameHandle,\n PTableColumnId,\n PColumnSpec,\n} from \"@platforma-sdk/model\";\nimport {\n Annotation,\n Domain,\n readAnnotation,\n readDomain,\n getUniqueAxisValuesWithLabels,\n getUniqueSourceValuesWithLabels,\n getPTableColumnId,\n deriveDistinctLabels,\n} from \"@platforma-sdk/model\";\nimport { computed, ref } from \"vue\";\nimport { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport {\n PlAdvancedFilter,\n PlAdvancedFilterComponent,\n type PlAdvancedFilterItem,\n} from \"../PlAdvancedFilter\";\nimport type { PlAdvancedFilterColumnId } from \"../PlAdvancedFilter/types\";\nimport type { Nil } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\nimport { resolveSuggestSource, TABLE_FILTER_TYPES } from \"./suggest-source\";\n\nconst props = defineProps<{\n columns: PTableColumnSpec[];\n pframeHandle: Nil | PFrameHandle;\n filters: PlDataTableFiltersWithMeta;\n defaultFilters: Nil | PlDataTableFiltersWithMeta;\n onUpdateFilters: (value: PlDataTableFiltersWithMeta) => void;\n onResetDefaultFilters?: () => void;\n onUpdateDefaultFilters?: (value: PlDataTableFiltersWithMeta) => void;\n}>();\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlTableFiltersV2\");\nconst showManager = ref(false);\nconst hasFilters = computed(() => props.filters.filters.length > 0);\nconst hasDefaultFilters = computed(\n () => !isNil(props.defaultFilters) && props.defaultFilters.filters.length > 0,\n);\nconst filters = computed<PlDataTableFiltersWithMeta>(() => {\n if (isNil(props.defaultFilters) || props.defaultFilters?.filters.length === 0) {\n return props.filters;\n }\n\n return {\n ...props.filters,\n filters: [...(props.defaultFilters?.filters ?? []), ...props.filters.filters],\n };\n});\nconst onUpdateFilters = (_value: PlAdvancedFilter) => {\n const value = _value as PlDataTableFiltersWithMeta;\n\n if (isNil(props.defaultFilters)) {\n return props.onUpdateFilters(value);\n }\n\n const [defaults, ...rest] = value.filters;\n props.onUpdateFilters({ ...value, filters: rest });\n props.onUpdateDefaultFilters?.({ ...props.defaultFilters, filters: [defaults] });\n};\n\nconst options = computed<PlAdvancedFilterItem[]>(() => {\n const columnEntries: { idx: number; spec: PColumnSpec }[] = [];\n props.columns.forEach((col, idx) => {\n if (col.type === \"column\") columnEntries.push({ idx, spec: col.spec });\n });\n const distinctLabelByIdx = new Map<number, string>();\n deriveDistinctLabels(\n columnEntries.map((e) => e.spec),\n {},\n ).forEach((label, i) => distinctLabelByIdx.set(columnEntries[i].idx, label));\n\n return props.columns.map((col, idx) => {\n const id = getPTableColumnId(col);\n const label =\n distinctLabelByIdx.get(idx)?.trim() ||\n readAnnotation(col.spec, Annotation.Label)?.trim() ||\n `Unlabeled ${col.type} ${idx}`;\n const alphabet =\n readDomain(col.spec, Domain.Alphabet) ?? readAnnotation(col.spec, Annotation.Alphabet);\n return {\n id,\n label,\n spec: col.spec,\n alphabet: alphabet ?? undefined,\n };\n });\n});\n\nconst supportedFilters = TABLE_FILTER_TYPES;\n\n// getSuggestOptions - provide discrete values from column annotations\nfunction handleSuggestOptions(params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchStr: string;\n searchType: \"value\" | \"label\";\n}) {\n if (isNil(props.pframeHandle)) {\n console.warn(\"PFrame handle is not available, cannot fetch suggest options\");\n return [];\n }\n\n const tableColumnId = params.columnId as PTableColumnId;\n if (typeof tableColumnId !== \"object\" || tableColumnId === null) {\n throw new Error(\"ColumnId should be a table column id for suggest options\");\n }\n\n const search = {\n limit: 100,\n searchQuery: params.searchType === \"label\" ? params.searchStr : undefined,\n searchQueryValue: params.searchType === \"value\" ? params.searchStr : undefined,\n };\n const source = resolveSuggestSource(props.columns, tableColumnId, params.axisIdx);\n const response =\n source.type === \"column\"\n ? getUniqueSourceValuesWithLabels(props.pframeHandle, {\n columnId: source.columnId,\n axisIdx: source.axisIdx,\n ...search,\n })\n : getUniqueAxisValuesWithLabels(props.pframeHandle, {\n axisSpec: source.axisSpec,\n parentColumnIds: source.parentColumnIds,\n ...search,\n });\n return response.then((v) => v.values);\n}\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget\" :to=\"teleportTarget\">\n <PlBtnGhost\n data-testid=\"pl-table-filters\"\n :icon=\"hasFilters || hasDefaultFilters ? 'filter-on' : 'filter'\"\n @click.stop=\"showManager = true\"\n >\n Filters\n </PlBtnGhost>\n </Teleport>\n\n <PlSlideModal\n v-model=\"showManager\"\n data-testid=\"pl-table-filters-panel\"\n :close-on-outside-click=\"false\"\n >\n <template #title>Manage Filters</template>\n\n <div data-testid=\"pl-table-filters-content\" :class=\"$style.root\">\n <PlAdvancedFilterComponent\n :filters=\"filters as PlAdvancedFilter\"\n :options=\"options\"\n :supported-filters=\"supportedFilters\"\n :get-suggest-options=\"handleSuggestOptions\"\n :is-pinned=\"(_, index) => hasDefaultFilters && index === 0\"\n :is-removable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :is-draggable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :enable-dnd=\"false\"\n :enable-toggling=\"true\"\n :enable-add-group-button=\"true\"\n @update-filters=\"onUpdateFilters\"\n >\n <template #group-title=\"{ index }\">\n <div\n v-if=\"hasDefaultFilters && index === 0\"\n data-testid=\"pl-table-filters-default-group-title\"\n :class=\"$style.defaultGroupTitle\"\n >\n Default Group\n <PlBtnGhost\n v-if=\"isFunction(props.onResetDefaultFilters)\"\n data-testid=\"pl-table-filters-reset-default\"\n icon=\"restart\"\n :class=\"$style.restartBtn\"\n @click.stop=\"props.onResetDefaultFilters()\"\n />\n </div>\n <div v-else data-testid=\"pl-table-filters-custom-group-title\">Custom Group</div>\n </template>\n </PlAdvancedFilterComponent>\n </div>\n </PlSlideModal>\n</template>\n\n<style module>\n.root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.defaultGroupTitle {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.restartBtn {\n width: 24px;\n height: 24px;\n padding: 4px;\n --button-width: 24px;\n --btn-min-width: 24px;\n --button-height: 24px;\n --btn-min-height: 24x;\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PlTableFiltersV2.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type {\n PTableColumnSpec,\n PlDataTableFiltersWithMeta,\n PFrameHandle,\n PTableColumnId,\n PColumnSpec,\n} from \"@platforma-sdk/model\";\nimport {\n Annotation,\n Domain,\n readAnnotation,\n readDomain,\n getUniqueSourceValuesWithLabels,\n extractPObjectId,\n getPTableColumnId,\n deriveDistinctLabels,\n} from \"@platforma-sdk/model\";\nimport { computed, ref } from \"vue\";\nimport { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport {\n PlAdvancedFilter,\n PlAdvancedFilterComponent,\n PlAdvancedFilterSupportedFilters,\n type PlAdvancedFilterItem,\n} from \"../PlAdvancedFilter\";\nimport type { PlAdvancedFilterColumnId } from \"../PlAdvancedFilter/types\";\nimport type { Nil } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n columns: PTableColumnSpec[];\n pframeHandle: Nil | PFrameHandle;\n filters: PlDataTableFiltersWithMeta;\n defaultFilters: Nil | PlDataTableFiltersWithMeta;\n onUpdateFilters: (value: PlDataTableFiltersWithMeta) => void;\n onResetDefaultFilters?: () => void;\n onUpdateDefaultFilters?: (value: PlDataTableFiltersWithMeta) => void;\n}>();\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlTableFiltersV2\");\nconst showManager = ref(false);\nconst hasFilters = computed(() => props.filters.filters.length > 0);\nconst hasDefaultFilters = computed(\n () => !isNil(props.defaultFilters) && props.defaultFilters.filters.length > 0,\n);\nconst filters = computed<PlDataTableFiltersWithMeta>(() => {\n if (isNil(props.defaultFilters) || props.defaultFilters?.filters.length === 0) {\n return props.filters;\n }\n\n return {\n ...props.filters,\n filters: [...(props.defaultFilters?.filters ?? []), ...props.filters.filters],\n };\n});\nconst onUpdateFilters = (_value: PlAdvancedFilter) => {\n const value = _value as PlDataTableFiltersWithMeta;\n\n if (isNil(props.defaultFilters)) {\n return props.onUpdateFilters(value);\n }\n\n const [defaults, ...rest] = value.filters;\n props.onUpdateFilters({ ...value, filters: rest });\n props.onUpdateDefaultFilters?.({ ...props.defaultFilters, filters: [defaults] });\n};\n\nconst options = computed<PlAdvancedFilterItem[]>(() => {\n const columnEntries: { idx: number; spec: PColumnSpec }[] = [];\n props.columns.forEach((col, idx) => {\n if (col.type === \"column\") columnEntries.push({ idx, spec: col.spec });\n });\n const distinctLabelByIdx = new Map<number, string>();\n deriveDistinctLabels(\n columnEntries.map((e) => e.spec),\n {},\n ).forEach((label, i) => distinctLabelByIdx.set(columnEntries[i].idx, label));\n\n return props.columns.map((col, idx) => {\n const id = getPTableColumnId(col);\n const label =\n distinctLabelByIdx.get(idx)?.trim() ||\n readAnnotation(col.spec, Annotation.Label)?.trim() ||\n `Unlabeled ${col.type} ${idx}`;\n const alphabet =\n readDomain(col.spec, Domain.Alphabet) ?? readAnnotation(col.spec, Annotation.Alphabet);\n return {\n id,\n label,\n spec: col.spec,\n alphabet: alphabet ?? undefined,\n };\n });\n});\n\n// Supported filters (same set as FilterSidebar)\nconst supportedFilters = [\n \"isNA\",\n \"isNotNA\",\n \"greaterThan\",\n \"greaterThanOrEqual\",\n \"lessThan\",\n \"lessThanOrEqual\",\n \"patternEquals\",\n \"patternNotEquals\",\n \"patternContainSubsequence\",\n \"patternNotContainSubsequence\",\n \"patternMatchesRegularExpression\",\n \"patternFuzzyContainSubsequence\",\n \"equal\",\n \"notEqual\",\n] as (typeof PlAdvancedFilterSupportedFilters)[number][];\n\n// getSuggestOptions - provide discrete values from column annotations\nfunction handleSuggestOptions(params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchStr: string;\n searchType: \"value\" | \"label\";\n}) {\n if (isNil(props.pframeHandle)) {\n console.warn(\"PFrame handle is not available, cannot fetch suggest options\");\n return [];\n }\n\n const tableColumnId = params.columnId as PTableColumnId;\n if (\n typeof tableColumnId !== \"object\" ||\n tableColumnId === null ||\n tableColumnId.type !== \"column\"\n ) {\n throw new Error(\"ColumnId should be of type 'column' for suggest options\");\n }\n\n return getUniqueSourceValuesWithLabels(props.pframeHandle, {\n columnId: extractPObjectId(tableColumnId.id),\n axisIdx: params.axisIdx,\n limit: 100,\n searchQuery: params.searchType === \"label\" ? params.searchStr : undefined,\n searchQueryValue: params.searchType === \"value\" ? params.searchStr : undefined,\n }).then((v) => v.values);\n}\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget\" :to=\"teleportTarget\">\n <PlBtnGhost\n data-testid=\"pl-table-filters\"\n :icon=\"hasFilters || hasDefaultFilters ? 'filter-on' : 'filter'\"\n @click.stop=\"showManager = true\"\n >\n Filters\n </PlBtnGhost>\n </Teleport>\n\n <PlSlideModal\n v-model=\"showManager\"\n data-testid=\"pl-table-filters-panel\"\n :close-on-outside-click=\"false\"\n >\n <template #title>Manage Filters</template>\n\n <div data-testid=\"pl-table-filters-content\" :class=\"$style.root\">\n <PlAdvancedFilterComponent\n :filters=\"filters as PlAdvancedFilter\"\n :options=\"options\"\n :supported-filters=\"supportedFilters\"\n :get-suggest-options=\"handleSuggestOptions\"\n :is-pinned=\"(_, index) => hasDefaultFilters && index === 0\"\n :is-removable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :is-draggable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :enable-dnd=\"false\"\n :enable-toggling=\"true\"\n :enable-add-group-button=\"true\"\n @update-filters=\"onUpdateFilters\"\n >\n <template #group-title=\"{ index }\">\n <div\n v-if=\"hasDefaultFilters && index === 0\"\n data-testid=\"pl-table-filters-default-group-title\"\n :class=\"$style.defaultGroupTitle\"\n >\n Default Group\n <PlBtnGhost\n v-if=\"isFunction(props.onResetDefaultFilters)\"\n data-testid=\"pl-table-filters-reset-default\"\n icon=\"restart\"\n :class=\"$style.restartBtn\"\n @click.stop=\"props.onResetDefaultFilters()\"\n />\n </div>\n <div v-else data-testid=\"pl-table-filters-custom-group-title\">Custom Group</div>\n </template>\n </PlAdvancedFilterComponent>\n </div>\n </PlSlideModal>\n</template>\n\n<style module>\n.root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.defaultGroupTitle {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.restartBtn {\n width: 24px;\n height: 24px;\n padding: 4px;\n --button-width: 24px;\n --btn-min-width: 24px;\n --button-height: 24px;\n --btn-min-height: 24x;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"PlTableFiltersV2.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type {\n PTableColumnSpec,\n PlDataTableFiltersWithMeta,\n PFrameHandle,\n PTableColumnId,\n PColumnSpec,\n} from \"@platforma-sdk/model\";\nimport {\n Annotation,\n Domain,\n readAnnotation,\n readDomain,\n getUniqueAxisValuesWithLabels,\n getUniqueSourceValuesWithLabels,\n getPTableColumnId,\n deriveDistinctLabels,\n} from \"@platforma-sdk/model\";\nimport { computed, ref } from \"vue\";\nimport { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport {\n PlAdvancedFilter,\n PlAdvancedFilterComponent,\n type PlAdvancedFilterItem,\n} from \"../PlAdvancedFilter\";\nimport type { PlAdvancedFilterColumnId } from \"../PlAdvancedFilter/types\";\nimport type { Nil } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\nimport { resolveSuggestSource, TABLE_FILTER_TYPES } from \"./suggest-source\";\n\nconst props = defineProps<{\n columns: PTableColumnSpec[];\n pframeHandle: Nil | PFrameHandle;\n filters: PlDataTableFiltersWithMeta;\n defaultFilters: Nil | PlDataTableFiltersWithMeta;\n onUpdateFilters: (value: PlDataTableFiltersWithMeta) => void;\n onResetDefaultFilters?: () => void;\n onUpdateDefaultFilters?: (value: PlDataTableFiltersWithMeta) => void;\n}>();\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlTableFiltersV2\");\nconst showManager = ref(false);\nconst hasFilters = computed(() => props.filters.filters.length > 0);\nconst hasDefaultFilters = computed(\n () => !isNil(props.defaultFilters) && props.defaultFilters.filters.length > 0,\n);\nconst filters = computed<PlDataTableFiltersWithMeta>(() => {\n if (isNil(props.defaultFilters) || props.defaultFilters?.filters.length === 0) {\n return props.filters;\n }\n\n return {\n ...props.filters,\n filters: [...(props.defaultFilters?.filters ?? []), ...props.filters.filters],\n };\n});\nconst onUpdateFilters = (_value: PlAdvancedFilter) => {\n const value = _value as PlDataTableFiltersWithMeta;\n\n if (isNil(props.defaultFilters)) {\n return props.onUpdateFilters(value);\n }\n\n const [defaults, ...rest] = value.filters;\n props.onUpdateFilters({ ...value, filters: rest });\n props.onUpdateDefaultFilters?.({ ...props.defaultFilters, filters: [defaults] });\n};\n\nconst options = computed<PlAdvancedFilterItem[]>(() => {\n const columnEntries: { idx: number; spec: PColumnSpec }[] = [];\n props.columns.forEach((col, idx) => {\n if (col.type === \"column\") columnEntries.push({ idx, spec: col.spec });\n });\n const distinctLabelByIdx = new Map<number, string>();\n deriveDistinctLabels(\n columnEntries.map((e) => e.spec),\n {},\n ).forEach((label, i) => distinctLabelByIdx.set(columnEntries[i].idx, label));\n\n return props.columns.map((col, idx) => {\n const id = getPTableColumnId(col);\n const label =\n distinctLabelByIdx.get(idx)?.trim() ||\n readAnnotation(col.spec, Annotation.Label)?.trim() ||\n `Unlabeled ${col.type} ${idx}`;\n const alphabet =\n readDomain(col.spec, Domain.Alphabet) ?? readAnnotation(col.spec, Annotation.Alphabet);\n return {\n id,\n label,\n spec: col.spec,\n alphabet: alphabet ?? undefined,\n };\n });\n});\n\nconst supportedFilters = TABLE_FILTER_TYPES;\n\n// getSuggestOptions - provide discrete values from column annotations\nfunction handleSuggestOptions(params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchStr: string;\n searchType: \"value\" | \"label\";\n}) {\n if (isNil(props.pframeHandle)) {\n console.warn(\"PFrame handle is not available, cannot fetch suggest options\");\n return [];\n }\n\n const tableColumnId = params.columnId as PTableColumnId;\n if (typeof tableColumnId !== \"object\" || tableColumnId === null) {\n throw new Error(\"ColumnId should be a table column id for suggest options\");\n }\n\n const search = {\n limit: 100,\n searchQuery: params.searchType === \"label\" ? params.searchStr : undefined,\n searchQueryValue: params.searchType === \"value\" ? params.searchStr : undefined,\n };\n const source = resolveSuggestSource(props.columns, tableColumnId, params.axisIdx);\n const response =\n source.type === \"column\"\n ? getUniqueSourceValuesWithLabels(props.pframeHandle, {\n columnId: source.columnId,\n axisIdx: source.axisIdx,\n ...search,\n })\n : getUniqueAxisValuesWithLabels(props.pframeHandle, {\n axisSpec: source.axisSpec,\n parentColumnIds: source.parentColumnIds,\n ...search,\n });\n return response.then((v) => v.values);\n}\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget\" :to=\"teleportTarget\">\n <PlBtnGhost\n data-testid=\"pl-table-filters\"\n :icon=\"hasFilters || hasDefaultFilters ? 'filter-on' : 'filter'\"\n @click.stop=\"showManager = true\"\n >\n Filters\n </PlBtnGhost>\n </Teleport>\n\n <PlSlideModal\n v-model=\"showManager\"\n data-testid=\"pl-table-filters-panel\"\n :close-on-outside-click=\"false\"\n >\n <template #title>Manage Filters</template>\n\n <div data-testid=\"pl-table-filters-content\" :class=\"$style.root\">\n <PlAdvancedFilterComponent\n :filters=\"filters as PlAdvancedFilter\"\n :options=\"options\"\n :supported-filters=\"supportedFilters\"\n :get-suggest-options=\"handleSuggestOptions\"\n :is-pinned=\"(_, index) => hasDefaultFilters && index === 0\"\n :is-removable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :is-draggable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :enable-dnd=\"false\"\n :enable-toggling=\"true\"\n :enable-add-group-button=\"true\"\n @update-filters=\"onUpdateFilters\"\n >\n <template #group-title=\"{ index }\">\n <div\n v-if=\"hasDefaultFilters && index === 0\"\n data-testid=\"pl-table-filters-default-group-title\"\n :class=\"$style.defaultGroupTitle\"\n >\n Default Group\n <PlBtnGhost\n v-if=\"isFunction(props.onResetDefaultFilters)\"\n data-testid=\"pl-table-filters-reset-default\"\n icon=\"restart\"\n :class=\"$style.restartBtn\"\n @click.stop=\"props.onResetDefaultFilters()\"\n />\n </div>\n <div v-else data-testid=\"pl-table-filters-custom-group-title\">Custom Group</div>\n </template>\n </PlAdvancedFilterComponent>\n </div>\n </PlSlideModal>\n</template>\n\n<style module>\n.root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.defaultGroupTitle {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.restartBtn {\n width: 24px;\n height: 24px;\n padding: 4px;\n --button-width: 24px;\n --btn-min-width: 24px;\n --button-height: 24px;\n --btn-min-height: 24x;\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PlTableFiltersV2.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"names":[],"mappings":"AA+NA,OAAO,KAAK,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAoB9B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAGnD,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,YAAY,EAAE,GAAG,GAAG,YAAY,CAAC;IACjC,OAAO,EAAE,0BAA0B,CAAC;IACpC,cAAc,EAAE,GAAG,GAAG,0BAA0B,CAAC;IACjD,eAAe,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC7D,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CACtE,CAAC;AAiTF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"PlTableFiltersV2.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"names":[],"mappings":"AAuNA,OAAO,KAAK,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,YAAY,EAGb,MAAM,sBAAsB,CAAC;AAmB9B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAInD,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,YAAY,EAAE,GAAG,GAAG,YAAY,CAAC;IACjC,OAAO,EAAE,0BAA0B,CAAC;IACpC,cAAc,EAAE,GAAG,GAAG,0BAA0B,CAAC;IACjD,eAAe,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC7D,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CACtE,CAAC;AAySF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1,14 +1,15 @@
1
1
  import e from "../PlAdvancedFilter/PlAdvancedFilter.js";
2
2
  import "../PlAdvancedFilter/index.js";
3
- import { Fragment as t, Teleport as n, computed as r, createBlock as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, createTextVNode as c, createVNode as l, defineComponent as u, normalizeClass as d, openBlock as f, ref as p, unref as m, withCtx as h, withModifiers as g } from "vue";
4
- import { Annotation as _, Domain as v, deriveDistinctLabels as y, extractPObjectId as b, getPTableColumnId as x, getUniqueSourceValuesWithLabels as S, readAnnotation as C, readDomain as w } from "@platforma-sdk/model";
5
- import { isFunction as T, isNil as E } from "es-toolkit";
6
- import { PlBtnGhost as D, PlSlideModal as O, usePlBlockPageTitleTeleportTarget as k } from "@milaboratories/uikit";
3
+ import { TABLE_FILTER_TYPES as t, resolveSuggestSource as n } from "./suggest-source.js";
4
+ import { Fragment as r, Teleport as i, computed as a, createBlock as o, createCommentVNode as s, createElementBlock as c, createElementVNode as l, createTextVNode as u, createVNode as d, defineComponent as f, normalizeClass as p, openBlock as m, ref as h, unref as g, withCtx as _, withModifiers as v } from "vue";
5
+ import { Annotation as y, Domain as b, deriveDistinctLabels as x, getPTableColumnId as S, getUniqueAxisValuesWithLabels as C, getUniqueSourceValuesWithLabels as w, readAnnotation as T, readDomain as E } from "@platforma-sdk/model";
6
+ import { isFunction as D, isNil as O } from "es-toolkit";
7
+ import { PlBtnGhost as k, PlSlideModal as A, usePlBlockPageTitleTeleportTarget as j } from "@milaboratories/uikit";
7
8
  //#region src/components/PlTableFilters/PlTableFiltersV2.vue?vue&type=script&setup=true&lang.ts
8
- var A = {
9
+ var M = {
9
10
  key: 1,
10
11
  "data-testid": "pl-table-filters-custom-group-title"
11
- }, j = /* @__PURE__ */ u({
12
+ }, N = /* @__PURE__ */ f({
12
13
  __name: "PlTableFiltersV2",
13
14
  props: {
14
15
  columns: {},
@@ -19,32 +20,32 @@ var A = {
19
20
  onResetDefaultFilters: { type: Function },
20
21
  onUpdateDefaultFilters: { type: Function }
21
22
  },
22
- setup(u) {
23
- let j = u, M = k("PlTableFiltersV2"), N = p(!1), P = r(() => j.filters.filters.length > 0), F = r(() => !E(j.defaultFilters) && j.defaultFilters.filters.length > 0), I = r(() => E(j.defaultFilters) || j.defaultFilters?.filters.length === 0 ? j.filters : {
24
- ...j.filters,
25
- filters: [...j.defaultFilters?.filters ?? [], ...j.filters.filters]
26
- }), L = (e) => {
23
+ setup(f) {
24
+ let N = f, P = j("PlTableFiltersV2"), F = h(!1), I = a(() => N.filters.filters.length > 0), L = a(() => !O(N.defaultFilters) && N.defaultFilters.filters.length > 0), R = a(() => O(N.defaultFilters) || N.defaultFilters?.filters.length === 0 ? N.filters : {
25
+ ...N.filters,
26
+ filters: [...N.defaultFilters?.filters ?? [], ...N.filters.filters]
27
+ }), z = (e) => {
27
28
  let t = e;
28
- if (E(j.defaultFilters)) return j.onUpdateFilters(t);
29
+ if (O(N.defaultFilters)) return N.onUpdateFilters(t);
29
30
  let [n, ...r] = t.filters;
30
- j.onUpdateFilters({
31
+ N.onUpdateFilters({
31
32
  ...t,
32
33
  filters: r
33
- }), j.onUpdateDefaultFilters?.({
34
- ...j.defaultFilters,
34
+ }), N.onUpdateDefaultFilters?.({
35
+ ...N.defaultFilters,
35
36
  filters: [n]
36
37
  });
37
- }, R = r(() => {
38
+ }, B = a(() => {
38
39
  let e = [];
39
- j.columns.forEach((t, n) => {
40
+ N.columns.forEach((t, n) => {
40
41
  t.type === "column" && e.push({
41
42
  idx: n,
42
43
  spec: t.spec
43
44
  });
44
45
  });
45
46
  let t = /* @__PURE__ */ new Map();
46
- return y(e.map((e) => e.spec), {}).forEach((n, r) => t.set(e[r].idx, n)), j.columns.map((e, n) => {
47
- let r = x(e), i = t.get(n)?.trim() || C(e.spec, _.Label)?.trim() || `Unlabeled ${e.type} ${n}`, a = w(e.spec, v.Alphabet) ?? C(e.spec, _.Alphabet);
47
+ return x(e.map((e) => e.spec), {}).forEach((n, r) => t.set(e[r].idx, n)), N.columns.map((e, n) => {
48
+ let r = S(e), i = t.get(n)?.trim() || T(e.spec, y.Label)?.trim() || `Unlabeled ${e.type} ${n}`, a = E(e.spec, b.Alphabet) ?? T(e.spec, y.Alphabet);
48
49
  return {
49
50
  id: r,
50
51
  label: i,
@@ -52,82 +53,75 @@ var A = {
52
53
  alphabet: a ?? void 0
53
54
  };
54
55
  });
55
- }), z = [
56
- "isNA",
57
- "isNotNA",
58
- "greaterThan",
59
- "greaterThanOrEqual",
60
- "lessThan",
61
- "lessThanOrEqual",
62
- "patternEquals",
63
- "patternNotEquals",
64
- "patternContainSubsequence",
65
- "patternNotContainSubsequence",
66
- "patternMatchesRegularExpression",
67
- "patternFuzzyContainSubsequence",
68
- "equal",
69
- "notEqual"
70
- ];
71
- function B(e) {
72
- if (E(j.pframeHandle)) return console.warn("PFrame handle is not available, cannot fetch suggest options"), [];
56
+ }), V = t;
57
+ function H(e) {
58
+ if (O(N.pframeHandle)) return console.warn("PFrame handle is not available, cannot fetch suggest options"), [];
73
59
  let t = e.columnId;
74
- if (typeof t != "object" || !t || t.type !== "column") throw Error("ColumnId should be of type 'column' for suggest options");
75
- return S(j.pframeHandle, {
76
- columnId: b(t.id),
77
- axisIdx: e.axisIdx,
60
+ if (typeof t != "object" || !t) throw Error("ColumnId should be a table column id for suggest options");
61
+ let r = {
78
62
  limit: 100,
79
63
  searchQuery: e.searchType === "label" ? e.searchStr : void 0,
80
64
  searchQueryValue: e.searchType === "value" ? e.searchStr : void 0
81
- }).then((e) => e.values);
65
+ }, i = n(N.columns, t, e.axisIdx);
66
+ return (i.type === "column" ? w(N.pframeHandle, {
67
+ columnId: i.columnId,
68
+ axisIdx: i.axisIdx,
69
+ ...r
70
+ }) : C(N.pframeHandle, {
71
+ axisSpec: i.axisSpec,
72
+ parentColumnIds: i.parentColumnIds,
73
+ ...r
74
+ })).then((e) => e.values);
82
75
  }
83
- return (r, u) => (f(), o(t, null, [m(M) ? (f(), i(n, {
76
+ return (t, n) => (m(), c(r, null, [g(P) ? (m(), o(i, {
84
77
  key: 0,
85
- to: m(M)
86
- }, [l(m(D), {
78
+ to: g(P)
79
+ }, [d(g(k), {
87
80
  "data-testid": "pl-table-filters",
88
- icon: P.value || F.value ? "filter-on" : "filter",
89
- onClick: u[0] ||= g((e) => N.value = !0, ["stop"])
81
+ icon: I.value || L.value ? "filter-on" : "filter",
82
+ onClick: n[0] ||= v((e) => F.value = !0, ["stop"])
90
83
  }, {
91
- default: h(() => [...u[3] ||= [c(" Filters ", -1)]]),
84
+ default: _(() => [...n[3] ||= [u(" Filters ", -1)]]),
92
85
  _: 1
93
- }, 8, ["icon"])], 8, ["to"])) : a("", !0), l(m(O), {
94
- modelValue: N.value,
95
- "onUpdate:modelValue": u[2] ||= (e) => N.value = e,
86
+ }, 8, ["icon"])], 8, ["to"])) : s("", !0), d(g(A), {
87
+ modelValue: F.value,
88
+ "onUpdate:modelValue": n[2] ||= (e) => F.value = e,
96
89
  "data-testid": "pl-table-filters-panel",
97
90
  "close-on-outside-click": !1
98
91
  }, {
99
- title: h(() => [...u[4] ||= [c("Manage Filters", -1)]]),
100
- default: h(() => [s("div", {
92
+ title: _(() => [...n[4] ||= [u("Manage Filters", -1)]]),
93
+ default: _(() => [l("div", {
101
94
  "data-testid": "pl-table-filters-content",
102
- class: d(r.$style.root)
103
- }, [l(m(e), {
104
- filters: I.value,
105
- options: R.value,
106
- "supported-filters": z,
107
- "get-suggest-options": B,
108
- "is-pinned": (e, t) => F.value && t === 0,
109
- "is-removable": (e, t) => F.value ? t > 0 : !0,
110
- "is-draggable": (e, t) => F.value ? t > 0 : !0,
95
+ class: p(t.$style.root)
96
+ }, [d(g(e), {
97
+ filters: R.value,
98
+ options: B.value,
99
+ "supported-filters": g(V),
100
+ "get-suggest-options": H,
101
+ "is-pinned": (e, t) => L.value && t === 0,
102
+ "is-removable": (e, t) => L.value ? t > 0 : !0,
103
+ "is-draggable": (e, t) => L.value ? t > 0 : !0,
111
104
  "enable-dnd": !1,
112
105
  "enable-toggling": !0,
113
106
  "enable-add-group-button": !0,
114
- onUpdateFilters: L
107
+ onUpdateFilters: z
115
108
  }, {
116
- "group-title": h(({ index: e }) => [F.value && e === 0 ? (f(), o("div", {
109
+ "group-title": _(({ index: e }) => [L.value && e === 0 ? (m(), c("div", {
117
110
  key: 0,
118
111
  "data-testid": "pl-table-filters-default-group-title",
119
- class: d(r.$style.defaultGroupTitle)
120
- }, [u[5] ||= c(" Default Group ", -1), m(T)(j.onResetDefaultFilters) ? (f(), i(m(D), {
112
+ class: p(t.$style.defaultGroupTitle)
113
+ }, [n[5] ||= u(" Default Group ", -1), g(D)(N.onResetDefaultFilters) ? (m(), o(g(k), {
121
114
  key: 0,
122
115
  "data-testid": "pl-table-filters-reset-default",
123
116
  icon: "restart",
124
- class: d(r.$style.restartBtn),
125
- onClick: u[1] ||= g((e) => j.onResetDefaultFilters(), ["stop"])
126
- }, null, 8, ["class"])) : a("", !0)], 2)) : (f(), o("div", A, "Custom Group"))]),
117
+ class: p(t.$style.restartBtn),
118
+ onClick: n[1] ||= v((e) => N.onResetDefaultFilters(), ["stop"])
119
+ }, null, 8, ["class"])) : s("", !0)], 2)) : (m(), c("div", M, "Custom Group"))]),
127
120
  _: 1
128
121
  }, 8, [
129
122
  "filters",
130
123
  "options",
124
+ "supported-filters",
131
125
  "is-pinned",
132
126
  "is-removable",
133
127
  "is-draggable"
@@ -137,6 +131,6 @@ var A = {
137
131
  }
138
132
  });
139
133
  //#endregion
140
- export { j as default };
134
+ export { N as default };
141
135
 
142
136
  //# sourceMappingURL=PlTableFiltersV2.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlTableFiltersV2.vue_vue_type_script_setup_true_lang.js","names":["$style"],"sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type {\n PTableColumnSpec,\n PlDataTableFiltersWithMeta,\n PFrameHandle,\n PTableColumnId,\n PColumnSpec,\n} from \"@platforma-sdk/model\";\nimport {\n Annotation,\n Domain,\n readAnnotation,\n readDomain,\n getUniqueSourceValuesWithLabels,\n extractPObjectId,\n getPTableColumnId,\n deriveDistinctLabels,\n} from \"@platforma-sdk/model\";\nimport { computed, ref } from \"vue\";\nimport { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport {\n PlAdvancedFilter,\n PlAdvancedFilterComponent,\n PlAdvancedFilterSupportedFilters,\n type PlAdvancedFilterItem,\n} from \"../PlAdvancedFilter\";\nimport type { PlAdvancedFilterColumnId } from \"../PlAdvancedFilter/types\";\nimport type { Nil } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n columns: PTableColumnSpec[];\n pframeHandle: Nil | PFrameHandle;\n filters: PlDataTableFiltersWithMeta;\n defaultFilters: Nil | PlDataTableFiltersWithMeta;\n onUpdateFilters: (value: PlDataTableFiltersWithMeta) => void;\n onResetDefaultFilters?: () => void;\n onUpdateDefaultFilters?: (value: PlDataTableFiltersWithMeta) => void;\n}>();\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlTableFiltersV2\");\nconst showManager = ref(false);\nconst hasFilters = computed(() => props.filters.filters.length > 0);\nconst hasDefaultFilters = computed(\n () => !isNil(props.defaultFilters) && props.defaultFilters.filters.length > 0,\n);\nconst filters = computed<PlDataTableFiltersWithMeta>(() => {\n if (isNil(props.defaultFilters) || props.defaultFilters?.filters.length === 0) {\n return props.filters;\n }\n\n return {\n ...props.filters,\n filters: [...(props.defaultFilters?.filters ?? []), ...props.filters.filters],\n };\n});\nconst onUpdateFilters = (_value: PlAdvancedFilter) => {\n const value = _value as PlDataTableFiltersWithMeta;\n\n if (isNil(props.defaultFilters)) {\n return props.onUpdateFilters(value);\n }\n\n const [defaults, ...rest] = value.filters;\n props.onUpdateFilters({ ...value, filters: rest });\n props.onUpdateDefaultFilters?.({ ...props.defaultFilters, filters: [defaults] });\n};\n\nconst options = computed<PlAdvancedFilterItem[]>(() => {\n const columnEntries: { idx: number; spec: PColumnSpec }[] = [];\n props.columns.forEach((col, idx) => {\n if (col.type === \"column\") columnEntries.push({ idx, spec: col.spec });\n });\n const distinctLabelByIdx = new Map<number, string>();\n deriveDistinctLabels(\n columnEntries.map((e) => e.spec),\n {},\n ).forEach((label, i) => distinctLabelByIdx.set(columnEntries[i].idx, label));\n\n return props.columns.map((col, idx) => {\n const id = getPTableColumnId(col);\n const label =\n distinctLabelByIdx.get(idx)?.trim() ||\n readAnnotation(col.spec, Annotation.Label)?.trim() ||\n `Unlabeled ${col.type} ${idx}`;\n const alphabet =\n readDomain(col.spec, Domain.Alphabet) ?? readAnnotation(col.spec, Annotation.Alphabet);\n return {\n id,\n label,\n spec: col.spec,\n alphabet: alphabet ?? undefined,\n };\n });\n});\n\n// Supported filters (same set as FilterSidebar)\nconst supportedFilters = [\n \"isNA\",\n \"isNotNA\",\n \"greaterThan\",\n \"greaterThanOrEqual\",\n \"lessThan\",\n \"lessThanOrEqual\",\n \"patternEquals\",\n \"patternNotEquals\",\n \"patternContainSubsequence\",\n \"patternNotContainSubsequence\",\n \"patternMatchesRegularExpression\",\n \"patternFuzzyContainSubsequence\",\n \"equal\",\n \"notEqual\",\n] as (typeof PlAdvancedFilterSupportedFilters)[number][];\n\n// getSuggestOptions - provide discrete values from column annotations\nfunction handleSuggestOptions(params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchStr: string;\n searchType: \"value\" | \"label\";\n}) {\n if (isNil(props.pframeHandle)) {\n console.warn(\"PFrame handle is not available, cannot fetch suggest options\");\n return [];\n }\n\n const tableColumnId = params.columnId as PTableColumnId;\n if (\n typeof tableColumnId !== \"object\" ||\n tableColumnId === null ||\n tableColumnId.type !== \"column\"\n ) {\n throw new Error(\"ColumnId should be of type 'column' for suggest options\");\n }\n\n return getUniqueSourceValuesWithLabels(props.pframeHandle, {\n columnId: extractPObjectId(tableColumnId.id),\n axisIdx: params.axisIdx,\n limit: 100,\n searchQuery: params.searchType === \"label\" ? params.searchStr : undefined,\n searchQueryValue: params.searchType === \"value\" ? params.searchStr : undefined,\n }).then((v) => v.values);\n}\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget\" :to=\"teleportTarget\">\n <PlBtnGhost\n data-testid=\"pl-table-filters\"\n :icon=\"hasFilters || hasDefaultFilters ? 'filter-on' : 'filter'\"\n @click.stop=\"showManager = true\"\n >\n Filters\n </PlBtnGhost>\n </Teleport>\n\n <PlSlideModal\n v-model=\"showManager\"\n data-testid=\"pl-table-filters-panel\"\n :close-on-outside-click=\"false\"\n >\n <template #title>Manage Filters</template>\n\n <div data-testid=\"pl-table-filters-content\" :class=\"$style.root\">\n <PlAdvancedFilterComponent\n :filters=\"filters as PlAdvancedFilter\"\n :options=\"options\"\n :supported-filters=\"supportedFilters\"\n :get-suggest-options=\"handleSuggestOptions\"\n :is-pinned=\"(_, index) => hasDefaultFilters && index === 0\"\n :is-removable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :is-draggable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :enable-dnd=\"false\"\n :enable-toggling=\"true\"\n :enable-add-group-button=\"true\"\n @update-filters=\"onUpdateFilters\"\n >\n <template #group-title=\"{ index }\">\n <div\n v-if=\"hasDefaultFilters && index === 0\"\n data-testid=\"pl-table-filters-default-group-title\"\n :class=\"$style.defaultGroupTitle\"\n >\n Default Group\n <PlBtnGhost\n v-if=\"isFunction(props.onResetDefaultFilters)\"\n data-testid=\"pl-table-filters-reset-default\"\n icon=\"restart\"\n :class=\"$style.restartBtn\"\n @click.stop=\"props.onResetDefaultFilters()\"\n />\n </div>\n <div v-else data-testid=\"pl-table-filters-custom-group-title\">Custom Group</div>\n </template>\n </PlAdvancedFilterComponent>\n </div>\n </PlSlideModal>\n</template>\n\n<style module>\n.root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.defaultGroupTitle {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.restartBtn {\n width: 24px;\n height: 24px;\n padding: 4px;\n --button-width: 24px;\n --btn-min-width: 24px;\n --button-height: 24px;\n --btn-min-height: 24x;\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlTableFiltersV2.vue_vue_type_script_setup_true_lang.js","names":["$style"],"sources":["../../../src/components/PlTableFilters/PlTableFiltersV2.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type {\n PTableColumnSpec,\n PlDataTableFiltersWithMeta,\n PFrameHandle,\n PTableColumnId,\n PColumnSpec,\n} from \"@platforma-sdk/model\";\nimport {\n Annotation,\n Domain,\n readAnnotation,\n readDomain,\n getUniqueAxisValuesWithLabels,\n getUniqueSourceValuesWithLabels,\n getPTableColumnId,\n deriveDistinctLabels,\n} from \"@platforma-sdk/model\";\nimport { computed, ref } from \"vue\";\nimport { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from \"@milaboratories/uikit\";\nimport {\n PlAdvancedFilter,\n PlAdvancedFilterComponent,\n type PlAdvancedFilterItem,\n} from \"../PlAdvancedFilter\";\nimport type { PlAdvancedFilterColumnId } from \"../PlAdvancedFilter/types\";\nimport type { Nil } from \"@milaboratories/helpers\";\nimport { isFunction, isNil } from \"es-toolkit\";\nimport { resolveSuggestSource, TABLE_FILTER_TYPES } from \"./suggest-source\";\n\nconst props = defineProps<{\n columns: PTableColumnSpec[];\n pframeHandle: Nil | PFrameHandle;\n filters: PlDataTableFiltersWithMeta;\n defaultFilters: Nil | PlDataTableFiltersWithMeta;\n onUpdateFilters: (value: PlDataTableFiltersWithMeta) => void;\n onResetDefaultFilters?: () => void;\n onUpdateDefaultFilters?: (value: PlDataTableFiltersWithMeta) => void;\n}>();\n\nconst teleportTarget = usePlBlockPageTitleTeleportTarget(\"PlTableFiltersV2\");\nconst showManager = ref(false);\nconst hasFilters = computed(() => props.filters.filters.length > 0);\nconst hasDefaultFilters = computed(\n () => !isNil(props.defaultFilters) && props.defaultFilters.filters.length > 0,\n);\nconst filters = computed<PlDataTableFiltersWithMeta>(() => {\n if (isNil(props.defaultFilters) || props.defaultFilters?.filters.length === 0) {\n return props.filters;\n }\n\n return {\n ...props.filters,\n filters: [...(props.defaultFilters?.filters ?? []), ...props.filters.filters],\n };\n});\nconst onUpdateFilters = (_value: PlAdvancedFilter) => {\n const value = _value as PlDataTableFiltersWithMeta;\n\n if (isNil(props.defaultFilters)) {\n return props.onUpdateFilters(value);\n }\n\n const [defaults, ...rest] = value.filters;\n props.onUpdateFilters({ ...value, filters: rest });\n props.onUpdateDefaultFilters?.({ ...props.defaultFilters, filters: [defaults] });\n};\n\nconst options = computed<PlAdvancedFilterItem[]>(() => {\n const columnEntries: { idx: number; spec: PColumnSpec }[] = [];\n props.columns.forEach((col, idx) => {\n if (col.type === \"column\") columnEntries.push({ idx, spec: col.spec });\n });\n const distinctLabelByIdx = new Map<number, string>();\n deriveDistinctLabels(\n columnEntries.map((e) => e.spec),\n {},\n ).forEach((label, i) => distinctLabelByIdx.set(columnEntries[i].idx, label));\n\n return props.columns.map((col, idx) => {\n const id = getPTableColumnId(col);\n const label =\n distinctLabelByIdx.get(idx)?.trim() ||\n readAnnotation(col.spec, Annotation.Label)?.trim() ||\n `Unlabeled ${col.type} ${idx}`;\n const alphabet =\n readDomain(col.spec, Domain.Alphabet) ?? readAnnotation(col.spec, Annotation.Alphabet);\n return {\n id,\n label,\n spec: col.spec,\n alphabet: alphabet ?? undefined,\n };\n });\n});\n\nconst supportedFilters = TABLE_FILTER_TYPES;\n\n// getSuggestOptions - provide discrete values from column annotations\nfunction handleSuggestOptions(params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchStr: string;\n searchType: \"value\" | \"label\";\n}) {\n if (isNil(props.pframeHandle)) {\n console.warn(\"PFrame handle is not available, cannot fetch suggest options\");\n return [];\n }\n\n const tableColumnId = params.columnId as PTableColumnId;\n if (typeof tableColumnId !== \"object\" || tableColumnId === null) {\n throw new Error(\"ColumnId should be a table column id for suggest options\");\n }\n\n const search = {\n limit: 100,\n searchQuery: params.searchType === \"label\" ? params.searchStr : undefined,\n searchQueryValue: params.searchType === \"value\" ? params.searchStr : undefined,\n };\n const source = resolveSuggestSource(props.columns, tableColumnId, params.axisIdx);\n const response =\n source.type === \"column\"\n ? getUniqueSourceValuesWithLabels(props.pframeHandle, {\n columnId: source.columnId,\n axisIdx: source.axisIdx,\n ...search,\n })\n : getUniqueAxisValuesWithLabels(props.pframeHandle, {\n axisSpec: source.axisSpec,\n parentColumnIds: source.parentColumnIds,\n ...search,\n });\n return response.then((v) => v.values);\n}\n</script>\n\n<template>\n <Teleport v-if=\"teleportTarget\" :to=\"teleportTarget\">\n <PlBtnGhost\n data-testid=\"pl-table-filters\"\n :icon=\"hasFilters || hasDefaultFilters ? 'filter-on' : 'filter'\"\n @click.stop=\"showManager = true\"\n >\n Filters\n </PlBtnGhost>\n </Teleport>\n\n <PlSlideModal\n v-model=\"showManager\"\n data-testid=\"pl-table-filters-panel\"\n :close-on-outside-click=\"false\"\n >\n <template #title>Manage Filters</template>\n\n <div data-testid=\"pl-table-filters-content\" :class=\"$style.root\">\n <PlAdvancedFilterComponent\n :filters=\"filters as PlAdvancedFilter\"\n :options=\"options\"\n :supported-filters=\"supportedFilters\"\n :get-suggest-options=\"handleSuggestOptions\"\n :is-pinned=\"(_, index) => hasDefaultFilters && index === 0\"\n :is-removable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :is-draggable=\"(_, index) => (hasDefaultFilters ? index > 0 : true)\"\n :enable-dnd=\"false\"\n :enable-toggling=\"true\"\n :enable-add-group-button=\"true\"\n @update-filters=\"onUpdateFilters\"\n >\n <template #group-title=\"{ index }\">\n <div\n v-if=\"hasDefaultFilters && index === 0\"\n data-testid=\"pl-table-filters-default-group-title\"\n :class=\"$style.defaultGroupTitle\"\n >\n Default Group\n <PlBtnGhost\n v-if=\"isFunction(props.onResetDefaultFilters)\"\n data-testid=\"pl-table-filters-reset-default\"\n icon=\"restart\"\n :class=\"$style.restartBtn\"\n @click.stop=\"props.onResetDefaultFilters()\"\n />\n </div>\n <div v-else data-testid=\"pl-table-filters-custom-group-title\">Custom Group</div>\n </template>\n </PlAdvancedFilterComponent>\n </div>\n </PlSlideModal>\n</template>\n\n<style module>\n.root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.defaultGroupTitle {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.restartBtn {\n width: 24px;\n height: 24px;\n padding: 4px;\n --button-width: 24px;\n --btn-min-width: 24px;\n --button-height: 24px;\n --btn-min-height: 24x;\n}\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { AxisSpec, PObjectId, PTableColumnId, PTableColumnSpec } from '@platforma-sdk/model';
2
+ import { SupportedFilterTypes } from '../PlAdvancedFilter/types';
3
+ /** Predicates the table filter offers (same set as FilterSidebar, plus list membership). */
4
+ export declare const TABLE_FILTER_TYPES: SupportedFilterTypes[];
5
+ /** Where value suggestions for a filtered table column come from. */
6
+ export type SuggestSource = {
7
+ type: "column";
8
+ columnId: PObjectId;
9
+ axisIdx: undefined | number;
10
+ } | {
11
+ type: "axis";
12
+ axisSpec: AxisSpec;
13
+ parentColumnIds: PObjectId[];
14
+ };
15
+ /**
16
+ * A data column suggests its own values. An axis suggests the keys of every
17
+ * table column carrying it: a full join can hold keys that only some of those
18
+ * columns have, and the user can filter by any of them.
19
+ */
20
+ export declare function resolveSuggestSource(columns: PTableColumnSpec[], tableColumnId: PTableColumnId, axisIdx: undefined | number): SuggestSource;
21
+ //# sourceMappingURL=suggest-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-source.d.ts","sourceRoot":"","sources":["../../../src/components/PlTableFilters/suggest-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAElG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,EAAE,oBAAoB,EAiBpD,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,eAAe,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,aAAa,EAAE,cAAc,EAC7B,OAAO,EAAE,SAAS,GAAG,MAAM,GAC1B,aAAa,CAqBf"}
@@ -0,0 +1,42 @@
1
+ import { canonicalizeAxisId as e, extractPObjectId as t, getAxisId as n } from "@platforma-sdk/model";
2
+ //#region src/components/PlTableFilters/suggest-source.ts
3
+ var r = [
4
+ "isNA",
5
+ "isNotNA",
6
+ "greaterThan",
7
+ "greaterThanOrEqual",
8
+ "lessThan",
9
+ "lessThanOrEqual",
10
+ "patternEquals",
11
+ "patternNotEquals",
12
+ "patternContainSubsequence",
13
+ "patternNotContainSubsequence",
14
+ "patternMatchesRegularExpression",
15
+ "patternFuzzyContainSubsequence",
16
+ "equal",
17
+ "notEqual",
18
+ "inSet",
19
+ "notInSet"
20
+ ];
21
+ function i(n, r, i) {
22
+ if (r.type === "column") return {
23
+ type: "column",
24
+ columnId: t(r.id),
25
+ axisIdx: i
26
+ };
27
+ let o = e(r.id), s, c = /* @__PURE__ */ new Set();
28
+ for (let r of n) r.type === "axis" ? e(r.id) === o && (s = r.spec) : a(r.spec.axesSpec, o) && c.add(t(r.id));
29
+ if (s === void 0 || c.size === 0) throw Error(`No column in the table carries axis ${r.id.name}, cannot fetch suggest options`);
30
+ return {
31
+ type: "axis",
32
+ axisSpec: s,
33
+ parentColumnIds: [...c]
34
+ };
35
+ }
36
+ function a(t, r) {
37
+ return t.some((t) => e(n(t)) === r);
38
+ }
39
+ //#endregion
40
+ export { r as TABLE_FILTER_TYPES, i as resolveSuggestSource };
41
+
42
+ //# sourceMappingURL=suggest-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-source.js","names":[],"sources":["../../../src/components/PlTableFilters/suggest-source.ts"],"sourcesContent":["import type { AxisSpec, PObjectId, PTableColumnId, PTableColumnSpec } from \"@platforma-sdk/model\";\nimport { canonicalizeAxisId, extractPObjectId, getAxisId } from \"@platforma-sdk/model\";\nimport type { SupportedFilterTypes } from \"../PlAdvancedFilter/types\";\n\n/** Predicates the table filter offers (same set as FilterSidebar, plus list membership). */\nexport const TABLE_FILTER_TYPES: SupportedFilterTypes[] = [\n \"isNA\",\n \"isNotNA\",\n \"greaterThan\",\n \"greaterThanOrEqual\",\n \"lessThan\",\n \"lessThanOrEqual\",\n \"patternEquals\",\n \"patternNotEquals\",\n \"patternContainSubsequence\",\n \"patternNotContainSubsequence\",\n \"patternMatchesRegularExpression\",\n \"patternFuzzyContainSubsequence\",\n \"equal\",\n \"notEqual\",\n \"inSet\",\n \"notInSet\",\n];\n\n/** Where value suggestions for a filtered table column come from. */\nexport type SuggestSource =\n | { type: \"column\"; columnId: PObjectId; axisIdx: undefined | number }\n | { type: \"axis\"; axisSpec: AxisSpec; parentColumnIds: PObjectId[] };\n\n/**\n * A data column suggests its own values. An axis suggests the keys of every\n * table column carrying it: a full join can hold keys that only some of those\n * columns have, and the user can filter by any of them.\n */\nexport function resolveSuggestSource(\n columns: PTableColumnSpec[],\n tableColumnId: PTableColumnId,\n axisIdx: undefined | number,\n): SuggestSource {\n if (tableColumnId.type === \"column\") {\n return { type: \"column\", columnId: extractPObjectId(tableColumnId.id), axisIdx };\n }\n\n const strAxisId = canonicalizeAxisId(tableColumnId.id);\n let axisSpec: undefined | AxisSpec;\n const parentColumnIds = new Set<PObjectId>();\n for (const col of columns) {\n if (col.type === \"axis\") {\n if (canonicalizeAxisId(col.id) === strAxisId) axisSpec = col.spec;\n } else if (carriesAxis(col.spec.axesSpec, strAxisId)) {\n parentColumnIds.add(extractPObjectId(col.id));\n }\n }\n if (axisSpec === undefined || parentColumnIds.size === 0) {\n throw new Error(\n `No column in the table carries axis ${tableColumnId.id.name}, cannot fetch suggest options`,\n );\n }\n return { type: \"axis\", axisSpec, parentColumnIds: [...parentColumnIds] };\n}\n\n// Internals\n\nfunction carriesAxis(axesSpec: AxisSpec[], strAxisId: string): boolean {\n return axesSpec.some((axis) => canonicalizeAxisId(getAxisId(axis)) === strAxisId);\n}\n"],"mappings":";;AAKA,IAAa,IAA6C;CACxD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAYD,SAAgB,EACd,GACA,GACA,GACe;AACf,KAAI,EAAc,SAAS,SACzB,QAAO;EAAE,MAAM;EAAU,UAAU,EAAiB,EAAc,GAAG;EAAE;EAAS;CAGlF,IAAM,IAAY,EAAmB,EAAc,GAAG,EAClD,GACE,oBAAkB,IAAI,KAAgB;AAC5C,MAAK,IAAM,KAAO,EAChB,CAAI,EAAI,SAAS,SACX,EAAmB,EAAI,GAAG,KAAK,MAAW,IAAW,EAAI,QACpD,EAAY,EAAI,KAAK,UAAU,EAAU,IAClD,EAAgB,IAAI,EAAiB,EAAI,GAAG,CAAC;AAGjD,KAAI,MAAa,KAAA,KAAa,EAAgB,SAAS,EACrD,OAAU,MACR,uCAAuC,EAAc,GAAG,KAAK,gCAC9D;AAEH,QAAO;EAAE,MAAM;EAAQ;EAAU,iBAAiB,CAAC,GAAG,EAAgB;EAAE;;AAK1E,SAAS,EAAY,GAAsB,GAA4B;AACrE,QAAO,EAAS,MAAM,MAAS,EAAmB,EAAU,EAAK,CAAC,KAAK,EAAU"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=suggest-source.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-source.test.d.ts","sourceRoot":"","sources":["../../../src/components/PlTableFilters/suggest-source.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.84.17",
3
+ "version": "1.84.19",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -27,10 +27,10 @@
27
27
  "vue": "3.5.24",
28
28
  "zod": "~3.25.76",
29
29
  "@milaboratories/columns-collection-driver": "0.2.9",
30
- "@milaboratories/uikit": "2.15.39",
31
- "@platforma-sdk/model": "1.84.17",
30
+ "@milaboratories/pf-spec-driver": "1.5.8",
31
+ "@milaboratories/uikit": "2.15.40",
32
32
  "@milaboratories/pl-model-common": "1.53.0",
33
- "@milaboratories/pf-spec-driver": "1.5.8"
33
+ "@platforma-sdk/model": "1.84.19"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@faker-js/faker": "^9.2.0",
@@ -46,10 +46,10 @@
46
46
  "typescript": "7.0.2",
47
47
  "vite": "^8.0.6",
48
48
  "vitest": "^4.1.3",
49
- "@milaboratories/build-configs": "2.0.2",
50
49
  "@milaboratories/helpers": "1.14.5",
51
- "@milaboratories/ts-configs": "1.4.0",
52
- "@milaboratories/ts-builder": "1.7.2"
50
+ "@milaboratories/build-configs": "2.0.2",
51
+ "@milaboratories/ts-builder": "1.7.2",
52
+ "@milaboratories/ts-configs": "1.4.0"
53
53
  },
54
54
  "scripts": {
55
55
  "dev": "ts-builder serve --target browser-lib",
@@ -11,8 +11,8 @@ import {
11
11
  Domain,
12
12
  readAnnotation,
13
13
  readDomain,
14
+ getUniqueAxisValuesWithLabels,
14
15
  getUniqueSourceValuesWithLabels,
15
- extractPObjectId,
16
16
  getPTableColumnId,
17
17
  deriveDistinctLabels,
18
18
  } from "@platforma-sdk/model";
@@ -21,12 +21,12 @@ import { PlBtnGhost, PlSlideModal, usePlBlockPageTitleTeleportTarget } from "@mi
21
21
  import {
22
22
  PlAdvancedFilter,
23
23
  PlAdvancedFilterComponent,
24
- PlAdvancedFilterSupportedFilters,
25
24
  type PlAdvancedFilterItem,
26
25
  } from "../PlAdvancedFilter";
27
26
  import type { PlAdvancedFilterColumnId } from "../PlAdvancedFilter/types";
28
27
  import type { Nil } from "@milaboratories/helpers";
29
28
  import { isFunction, isNil } from "es-toolkit";
29
+ import { resolveSuggestSource, TABLE_FILTER_TYPES } from "./suggest-source";
30
30
 
31
31
  const props = defineProps<{
32
32
  columns: PTableColumnSpec[];
@@ -94,23 +94,7 @@ const options = computed<PlAdvancedFilterItem[]>(() => {
94
94
  });
95
95
  });
96
96
 
97
- // Supported filters (same set as FilterSidebar)
98
- const supportedFilters = [
99
- "isNA",
100
- "isNotNA",
101
- "greaterThan",
102
- "greaterThanOrEqual",
103
- "lessThan",
104
- "lessThanOrEqual",
105
- "patternEquals",
106
- "patternNotEquals",
107
- "patternContainSubsequence",
108
- "patternNotContainSubsequence",
109
- "patternMatchesRegularExpression",
110
- "patternFuzzyContainSubsequence",
111
- "equal",
112
- "notEqual",
113
- ] as (typeof PlAdvancedFilterSupportedFilters)[number][];
97
+ const supportedFilters = TABLE_FILTER_TYPES;
114
98
 
115
99
  // getSuggestOptions - provide discrete values from column annotations
116
100
  function handleSuggestOptions(params: {
@@ -125,21 +109,29 @@ function handleSuggestOptions(params: {
125
109
  }
126
110
 
127
111
  const tableColumnId = params.columnId as PTableColumnId;
128
- if (
129
- typeof tableColumnId !== "object" ||
130
- tableColumnId === null ||
131
- tableColumnId.type !== "column"
132
- ) {
133
- throw new Error("ColumnId should be of type 'column' for suggest options");
112
+ if (typeof tableColumnId !== "object" || tableColumnId === null) {
113
+ throw new Error("ColumnId should be a table column id for suggest options");
134
114
  }
135
115
 
136
- return getUniqueSourceValuesWithLabels(props.pframeHandle, {
137
- columnId: extractPObjectId(tableColumnId.id),
138
- axisIdx: params.axisIdx,
116
+ const search = {
139
117
  limit: 100,
140
118
  searchQuery: params.searchType === "label" ? params.searchStr : undefined,
141
119
  searchQueryValue: params.searchType === "value" ? params.searchStr : undefined,
142
- }).then((v) => v.values);
120
+ };
121
+ const source = resolveSuggestSource(props.columns, tableColumnId, params.axisIdx);
122
+ const response =
123
+ source.type === "column"
124
+ ? getUniqueSourceValuesWithLabels(props.pframeHandle, {
125
+ columnId: source.columnId,
126
+ axisIdx: source.axisIdx,
127
+ ...search,
128
+ })
129
+ : getUniqueAxisValuesWithLabels(props.pframeHandle, {
130
+ axisSpec: source.axisSpec,
131
+ parentColumnIds: source.parentColumnIds,
132
+ ...search,
133
+ });
134
+ return response.then((v) => v.values);
143
135
  }
144
136
  </script>
145
137
 
@@ -0,0 +1,80 @@
1
+ import {
2
+ createGlobalPObjectId,
3
+ type AxisSpec,
4
+ type PColumnSpec,
5
+ type PObjectId,
6
+ type PTableColumnSpec,
7
+ } from "@platforma-sdk/model";
8
+ import { describe, expect, it } from "vitest";
9
+ import { resolveSuggestSource, TABLE_FILTER_TYPES } from "./suggest-source";
10
+
11
+ describe("TABLE_FILTER_TYPES", () => {
12
+ it("offers list membership predicates", () => {
13
+ expect(TABLE_FILTER_TYPES).toContain("inSet");
14
+ expect(TABLE_FILTER_TYPES).toContain("notInSet");
15
+ });
16
+ });
17
+
18
+ describe("resolveSuggestSource", () => {
19
+ const sampleAxis = axisSpec("pl7.app/sampleId");
20
+ const cloneAxis = axisSpec("pl7.app/vdj/clonotypeKey");
21
+ const abundanceId = columnId("abundance");
22
+ const countId = columnId("count");
23
+ const cloneLabelId = columnId("cloneLabel");
24
+
25
+ const columns: PTableColumnSpec[] = [
26
+ { type: "axis", id: sampleAxis, spec: sampleAxis },
27
+ { type: "axis", id: cloneAxis, spec: cloneAxis },
28
+ { type: "column", id: abundanceId, spec: columnSpec("abundance", [sampleAxis, cloneAxis]) },
29
+ { type: "column", id: countId, spec: columnSpec("count", [sampleAxis]) },
30
+ { type: "column", id: cloneLabelId, spec: columnSpec("label", [cloneAxis]) },
31
+ ];
32
+
33
+ it("reads a data column through the column itself", () => {
34
+ expect(resolveSuggestSource(columns, { type: "column", id: abundanceId }, 1)).toEqual({
35
+ type: "column",
36
+ columnId: abundanceId,
37
+ axisIdx: 1,
38
+ });
39
+ });
40
+
41
+ it("reads an axis through every column carrying it", () => {
42
+ expect(resolveSuggestSource(columns, { type: "axis", id: sampleAxis }, undefined)).toEqual({
43
+ type: "axis",
44
+ axisSpec: sampleAxis,
45
+ parentColumnIds: [abundanceId, countId],
46
+ });
47
+ });
48
+
49
+ it("does not take a column without the axis as a parent", () => {
50
+ const source = resolveSuggestSource(columns, { type: "axis", id: cloneAxis }, undefined);
51
+ expect(source).toEqual({
52
+ type: "axis",
53
+ axisSpec: cloneAxis,
54
+ parentColumnIds: [abundanceId, cloneLabelId],
55
+ });
56
+ });
57
+
58
+ it("throws when no column carries the axis", () => {
59
+ const orphanAxis = axisSpec("pl7.app/orphan");
60
+ const withOrphan: PTableColumnSpec[] = [
61
+ ...columns,
62
+ { type: "axis", id: orphanAxis, spec: orphanAxis },
63
+ ];
64
+ expect(() =>
65
+ resolveSuggestSource(withOrphan, { type: "axis", id: orphanAxis }, undefined),
66
+ ).toThrow(/pl7\.app\/orphan/);
67
+ });
68
+ });
69
+
70
+ function axisSpec(name: string): AxisSpec {
71
+ return { name, type: "String" };
72
+ }
73
+
74
+ function columnSpec(name: string, axesSpec: AxisSpec[]): PColumnSpec {
75
+ return { kind: "PColumn", name, valueType: "Int", axesSpec };
76
+ }
77
+
78
+ function columnId(name: string): PObjectId {
79
+ return createGlobalPObjectId("block", name);
80
+ }
@@ -0,0 +1,66 @@
1
+ import type { AxisSpec, PObjectId, PTableColumnId, PTableColumnSpec } from "@platforma-sdk/model";
2
+ import { canonicalizeAxisId, extractPObjectId, getAxisId } from "@platforma-sdk/model";
3
+ import type { SupportedFilterTypes } from "../PlAdvancedFilter/types";
4
+
5
+ /** Predicates the table filter offers (same set as FilterSidebar, plus list membership). */
6
+ export const TABLE_FILTER_TYPES: SupportedFilterTypes[] = [
7
+ "isNA",
8
+ "isNotNA",
9
+ "greaterThan",
10
+ "greaterThanOrEqual",
11
+ "lessThan",
12
+ "lessThanOrEqual",
13
+ "patternEquals",
14
+ "patternNotEquals",
15
+ "patternContainSubsequence",
16
+ "patternNotContainSubsequence",
17
+ "patternMatchesRegularExpression",
18
+ "patternFuzzyContainSubsequence",
19
+ "equal",
20
+ "notEqual",
21
+ "inSet",
22
+ "notInSet",
23
+ ];
24
+
25
+ /** Where value suggestions for a filtered table column come from. */
26
+ export type SuggestSource =
27
+ | { type: "column"; columnId: PObjectId; axisIdx: undefined | number }
28
+ | { type: "axis"; axisSpec: AxisSpec; parentColumnIds: PObjectId[] };
29
+
30
+ /**
31
+ * A data column suggests its own values. An axis suggests the keys of every
32
+ * table column carrying it: a full join can hold keys that only some of those
33
+ * columns have, and the user can filter by any of them.
34
+ */
35
+ export function resolveSuggestSource(
36
+ columns: PTableColumnSpec[],
37
+ tableColumnId: PTableColumnId,
38
+ axisIdx: undefined | number,
39
+ ): SuggestSource {
40
+ if (tableColumnId.type === "column") {
41
+ return { type: "column", columnId: extractPObjectId(tableColumnId.id), axisIdx };
42
+ }
43
+
44
+ const strAxisId = canonicalizeAxisId(tableColumnId.id);
45
+ let axisSpec: undefined | AxisSpec;
46
+ const parentColumnIds = new Set<PObjectId>();
47
+ for (const col of columns) {
48
+ if (col.type === "axis") {
49
+ if (canonicalizeAxisId(col.id) === strAxisId) axisSpec = col.spec;
50
+ } else if (carriesAxis(col.spec.axesSpec, strAxisId)) {
51
+ parentColumnIds.add(extractPObjectId(col.id));
52
+ }
53
+ }
54
+ if (axisSpec === undefined || parentColumnIds.size === 0) {
55
+ throw new Error(
56
+ `No column in the table carries axis ${tableColumnId.id.name}, cannot fetch suggest options`,
57
+ );
58
+ }
59
+ return { type: "axis", axisSpec, parentColumnIds: [...parentColumnIds] };
60
+ }
61
+
62
+ // Internals
63
+
64
+ function carriesAxis(axesSpec: AxisSpec[], strAxisId: string): boolean {
65
+ return axesSpec.some((axis) => canonicalizeAxisId(getAxisId(axis)) === strAxisId);
66
+ }