@platforma-sdk/ui-vue 1.84.9 → 1.84.14

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 (185) hide show
  1. package/.turbo/turbo-build.log +68 -68
  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 +19 -0
  6. package/dist/components/BlockLayout.js.map +1 -1
  7. package/dist/components/BlockLayout.style.js.map +1 -1
  8. package/dist/components/BlockLayout.vue.d.ts.map +1 -1
  9. package/dist/components/BlockLayout.vue2.js +5 -1
  10. package/dist/components/BlockLayout.vue2.js.map +1 -1
  11. package/dist/components/BlockLoader.js.map +1 -1
  12. package/dist/components/BlockLoader.vue.d.ts.map +1 -1
  13. package/dist/components/BlockLoader.vue2.js +2 -0
  14. package/dist/components/BlockLoader.vue2.js.map +1 -1
  15. package/dist/components/LoaderPage.js +4 -1
  16. package/dist/components/LoaderPage.js.map +1 -1
  17. package/dist/components/LoaderPage.vue.d.ts.map +1 -1
  18. package/dist/components/NotFound.js.map +1 -1
  19. package/dist/components/NotFound.vue.d.ts.map +1 -1
  20. package/dist/components/NotFound.vue2.js +11 -5
  21. package/dist/components/NotFound.vue2.js.map +1 -1
  22. package/dist/components/PlAdvancedFilter/FilterEditor.js.map +1 -1
  23. package/dist/components/PlAdvancedFilter/FilterEditor.style.js.map +1 -1
  24. package/dist/components/PlAdvancedFilter/FilterEditor.vue.d.ts.map +1 -1
  25. package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js +26 -2
  26. package/dist/components/PlAdvancedFilter/FilterEditor.vue2.js.map +1 -1
  27. package/dist/components/PlAdvancedFilter/OperandButton.js.map +1 -1
  28. package/dist/components/PlAdvancedFilter/OperandButton.style.js.map +1 -1
  29. package/dist/components/PlAdvancedFilter/OperandButton.vue.d.ts.map +1 -1
  30. package/dist/components/PlAdvancedFilter/OperandButton.vue2.js +2 -1
  31. package/dist/components/PlAdvancedFilter/OperandButton.vue2.js.map +1 -1
  32. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.js.map +1 -1
  33. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.style.js.map +1 -1
  34. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue.d.ts.map +1 -1
  35. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js +70 -61
  36. package/dist/components/PlAdvancedFilter/PlAdvancedFilter.vue2.js.map +1 -1
  37. package/dist/components/PlAgCellFile/PlAgCellFile.js.map +1 -1
  38. package/dist/components/PlAgCellFile/PlAgCellFile.vue.d.ts.map +1 -1
  39. package/dist/components/PlAgCellFile/PlAgCellFile.vue2.js +5 -1
  40. package/dist/components/PlAgCellFile/PlAgCellFile.vue2.js.map +1 -1
  41. package/dist/components/PlAgCellProgress/PlAgCellProgress.js.map +1 -1
  42. package/dist/components/PlAgCellProgress/PlAgCellProgress.vue.d.ts.map +1 -1
  43. package/dist/components/PlAgCellProgress/PlAgCellProgress.vue2.js +6 -6
  44. package/dist/components/PlAgCellProgress/PlAgCellProgress.vue2.js.map +1 -1
  45. package/dist/components/PlAgCellStatusTag/PlAgCellStatusTag.js.map +1 -1
  46. package/dist/components/PlAgCellStatusTag/PlAgCellStatusTag.vue.d.ts.map +1 -1
  47. package/dist/components/PlAgCellStatusTag/PlAgCellStatusTag.vue2.js +8 -2
  48. package/dist/components/PlAgCellStatusTag/PlAgCellStatusTag.vue2.js.map +1 -1
  49. package/dist/components/PlAgChartHistogramCell/PlAgChartHistogramCell.js.map +1 -1
  50. package/dist/components/PlAgChartHistogramCell/PlAgChartHistogramCell.vue.d.ts.map +1 -1
  51. package/dist/components/PlAgChartHistogramCell/PlAgChartHistogramCell.vue2.js +4 -1
  52. package/dist/components/PlAgChartHistogramCell/PlAgChartHistogramCell.vue2.js.map +1 -1
  53. package/dist/components/PlAgChartStackedBarCell/PlAgChartStackedBarCell.js.map +1 -1
  54. package/dist/components/PlAgChartStackedBarCell/PlAgChartStackedBarCell.vue.d.ts.map +1 -1
  55. package/dist/components/PlAgChartStackedBarCell/PlAgChartStackedBarCell.vue2.js +7 -2
  56. package/dist/components/PlAgChartStackedBarCell/PlAgChartStackedBarCell.vue2.js.map +1 -1
  57. package/dist/components/PlAgColumnHeader/PlAgColumnHeader.js.map +1 -1
  58. package/dist/components/PlAgColumnHeader/PlAgColumnHeader.vue.d.ts.map +1 -1
  59. package/dist/components/PlAgColumnHeader/PlAgColumnHeader.vue2.js +28 -20
  60. package/dist/components/PlAgColumnHeader/PlAgColumnHeader.vue2.js.map +1 -1
  61. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.js.map +1 -1
  62. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue.d.ts.map +1 -1
  63. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue2.js +1 -0
  64. package/dist/components/PlAgCsvExporter/PlAgCsvExporter.vue2.js.map +1 -1
  65. package/dist/components/PlAgDataTable/PlAgDataTableSheets.js.map +1 -1
  66. package/dist/components/PlAgDataTable/PlAgDataTableSheets.style.js.map +1 -1
  67. package/dist/components/PlAgDataTable/PlAgDataTableSheets.vue.d.ts.map +1 -1
  68. package/dist/components/PlAgDataTable/PlAgDataTableSheets.vue2.js +2 -0
  69. package/dist/components/PlAgDataTable/PlAgDataTableSheets.vue2.js.map +1 -1
  70. package/dist/components/PlAgDataTable/PlAgDataTableV2.js.map +1 -1
  71. package/dist/components/PlAgDataTable/PlAgDataTableV2.style.js.map +1 -1
  72. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue.d.ts.map +1 -1
  73. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js +5 -1
  74. package/dist/components/PlAgDataTable/PlAgDataTableV2.vue2.js.map +1 -1
  75. package/dist/components/PlAgDataTable/PlAgOverlayLoading.js.map +1 -1
  76. package/dist/components/PlAgDataTable/PlAgOverlayLoading.vue.d.ts.map +1 -1
  77. package/dist/components/PlAgDataTable/PlAgOverlayLoading.vue2.js +12 -2
  78. package/dist/components/PlAgDataTable/PlAgOverlayLoading.vue2.js.map +1 -1
  79. package/dist/components/PlAgDataTable/PlAgOverlayNoRows.js.map +1 -1
  80. package/dist/components/PlAgDataTable/PlAgOverlayNoRows.vue.d.ts.map +1 -1
  81. package/dist/components/PlAgDataTable/PlAgOverlayNoRows.vue2.js +7 -1
  82. package/dist/components/PlAgDataTable/PlAgOverlayNoRows.vue2.js.map +1 -1
  83. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.js.map +1 -1
  84. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.style.js.map +1 -1
  85. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue.d.ts.map +1 -1
  86. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue2.js +35 -27
  87. package/dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue2.js.map +1 -1
  88. package/dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.js.map +1 -1
  89. package/dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue.d.ts.map +1 -1
  90. package/dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue2.js +12 -7
  91. package/dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue2.js.map +1 -1
  92. package/dist/components/PlAgRowNumHeader.js.map +1 -1
  93. package/dist/components/PlAgRowNumHeader.vue.d.ts.map +1 -1
  94. package/dist/components/PlAgRowNumHeader.vue2.js +14 -7
  95. package/dist/components/PlAgRowNumHeader.vue2.js.map +1 -1
  96. package/dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.js.map +1 -1
  97. package/dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue.d.ts.map +1 -1
  98. package/dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue2.js +8 -1
  99. package/dist/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue2.js.map +1 -1
  100. package/dist/components/PlAnnotations/components/AnnotationsSidebar.js.map +1 -1
  101. package/dist/components/PlAnnotations/components/AnnotationsSidebar.style.js.map +1 -1
  102. package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue.d.ts.map +1 -1
  103. package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue2.js +14 -3
  104. package/dist/components/PlAnnotations/components/AnnotationsSidebar.vue2.js.map +1 -1
  105. package/dist/components/PlAnnotations/components/FilterSidebar.js.map +1 -1
  106. package/dist/components/PlAnnotations/components/FilterSidebar.style.js.map +1 -1
  107. package/dist/components/PlAnnotations/components/FilterSidebar.vue.d.ts.map +1 -1
  108. package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js +11 -2
  109. package/dist/components/PlAnnotations/components/FilterSidebar.vue2.js.map +1 -1
  110. package/dist/components/PlAnnotations/components/PlAnnotations.js.map +1 -1
  111. package/dist/components/PlAnnotations/components/PlAnnotations.style.js.map +1 -1
  112. package/dist/components/PlAnnotations/components/PlAnnotations.vue.d.ts.map +1 -1
  113. package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js +1 -1
  114. package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js.map +1 -1
  115. package/dist/components/PlAnnotations/components/PlAnnotationsModal.js.map +1 -1
  116. package/dist/components/PlAnnotations/components/PlAnnotationsModal.style.js.map +1 -1
  117. package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue.d.ts.map +1 -1
  118. package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue2.js +1 -0
  119. package/dist/components/PlAnnotations/components/PlAnnotationsModal.vue2.js.map +1 -1
  120. package/dist/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.js.map +1 -1
  121. package/dist/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.vue.d.ts.map +1 -1
  122. package/dist/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.vue2.js +16 -2
  123. package/dist/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.vue2.js.map +1 -1
  124. package/dist/components/PlBtnExportArchive/Item.js.map +1 -1
  125. package/dist/components/PlBtnExportArchive/Item.style.js.map +1 -1
  126. package/dist/components/PlBtnExportArchive/Item.vue.d.ts.map +1 -1
  127. package/dist/components/PlBtnExportArchive/Item.vue2.js +12 -2
  128. package/dist/components/PlBtnExportArchive/Item.vue2.js.map +1 -1
  129. package/dist/components/PlBtnExportArchive/PlBtnExportArchive.js.map +1 -1
  130. package/dist/components/PlBtnExportArchive/PlBtnExportArchive.style.js.map +1 -1
  131. package/dist/components/PlBtnExportArchive/PlBtnExportArchive.vue.d.ts.map +1 -1
  132. package/dist/components/PlBtnExportArchive/PlBtnExportArchive.vue2.js +7 -1
  133. package/dist/components/PlBtnExportArchive/PlBtnExportArchive.vue2.js.map +1 -1
  134. package/dist/components/PlBtnExportArchive/Summary.js.map +1 -1
  135. package/dist/components/PlBtnExportArchive/Summary.style.js.map +1 -1
  136. package/dist/components/PlBtnExportArchive/Summary.vue.d.ts.map +1 -1
  137. package/dist/components/PlBtnExportArchive/Summary.vue2.js +11 -2
  138. package/dist/components/PlBtnExportArchive/Summary.vue2.js.map +1 -1
  139. package/dist/components/PlDatasetSelector/PlDatasetSelector.js.map +1 -1
  140. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue.d.ts.map +1 -1
  141. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue2.js +1 -0
  142. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue2.js.map +1 -1
  143. package/dist/components/PlTableFastSearch/PlTableFastSearch.js.map +1 -1
  144. package/dist/components/PlTableFastSearch/PlTableFastSearch.vue.d.ts.map +1 -1
  145. package/dist/components/PlTableFastSearch/PlTableFastSearch.vue2.js +1 -0
  146. package/dist/components/PlTableFastSearch/PlTableFastSearch.vue2.js.map +1 -1
  147. package/dist/components/PlTableFilters/PlTableFiltersV2.js.map +1 -1
  148. package/dist/components/PlTableFilters/PlTableFiltersV2.style.js.map +1 -1
  149. package/dist/components/PlTableFilters/PlTableFiltersV2.vue.d.ts.map +1 -1
  150. package/dist/components/PlTableFilters/PlTableFiltersV2.vue2.js +12 -2
  151. package/dist/components/PlTableFilters/PlTableFiltersV2.vue2.js.map +1 -1
  152. package/package.json +6 -6
  153. package/src/components/BlockLayout.vue +4 -2
  154. package/src/components/BlockLoader.vue +2 -1
  155. package/src/components/LoaderPage.vue +1 -1
  156. package/src/components/NotFound.vue +7 -3
  157. package/src/components/PlAdvancedFilter/FilterEditor.vue +36 -6
  158. package/src/components/PlAdvancedFilter/OperandButton.vue +2 -1
  159. package/src/components/PlAdvancedFilter/PlAdvancedFilter.vue +20 -4
  160. package/src/components/PlAgCellFile/PlAgCellFile.vue +2 -1
  161. package/src/components/PlAgCellProgress/PlAgCellProgress.vue +1 -1
  162. package/src/components/PlAgCellStatusTag/PlAgCellStatusTag.vue +2 -2
  163. package/src/components/PlAgChartHistogramCell/PlAgChartHistogramCell.vue +13 -3
  164. package/src/components/PlAgChartStackedBarCell/PlAgChartStackedBarCell.vue +13 -3
  165. package/src/components/PlAgColumnHeader/PlAgColumnHeader.vue +24 -6
  166. package/src/components/PlAgCsvExporter/PlAgCsvExporter.vue +6 -1
  167. package/src/components/PlAgDataTable/PlAgDataTableSheets.vue +6 -1
  168. package/src/components/PlAgDataTable/PlAgDataTableV2.vue +2 -1
  169. package/src/components/PlAgDataTable/PlAgOverlayLoading.vue +10 -3
  170. package/src/components/PlAgDataTable/PlAgOverlayNoRows.vue +4 -2
  171. package/src/components/PlAgGridColumnManager/PlAgGridColumnManager.vue +18 -5
  172. package/src/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue +5 -1
  173. package/src/components/PlAgRowNumHeader.vue +3 -1
  174. package/src/components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.vue +3 -2
  175. package/src/components/PlAnnotations/components/AnnotationsSidebar.vue +13 -4
  176. package/src/components/PlAnnotations/components/FilterSidebar.vue +11 -3
  177. package/src/components/PlAnnotations/components/PlAnnotations.vue +1 -1
  178. package/src/components/PlAnnotations/components/PlAnnotationsModal.vue +1 -0
  179. package/src/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.vue +34 -7
  180. package/src/components/PlBtnExportArchive/Item.vue +22 -6
  181. package/src/components/PlBtnExportArchive/PlBtnExportArchive.vue +18 -3
  182. package/src/components/PlBtnExportArchive/Summary.vue +15 -5
  183. package/src/components/PlDatasetSelector/PlDatasetSelector.vue +1 -0
  184. package/src/components/PlTableFastSearch/PlTableFastSearch.vue +6 -1
  185. package/src/components/PlTableFilters/PlTableFiltersV2.vue +14 -4
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEditor.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n PTableColumnId,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n canonicalizeJson,\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) =>\n columnIdEquals(v.id, getSourceId(newSourceId)),\n );\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find((op) =>\n columnIdEquals(op.id, getSourceId(props.filter.column)),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction isPTableColumnIdObject(c: PlAdvancedFilterColumnId): c is PTableColumnId {\n return typeof c === \"object\" && c !== null;\n}\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n // PTableColumnId object form: no axisFilters embedded, return as-is.\n if (isPTableColumnIdObject(column)) return column;\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => columnIdEquals(op.id, sourceId));\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n // PTableColumnId object form has no axis filters baked in.\n if (isPTableColumnIdObject(column)) {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: (parsedColumnId.axisFilters as AxisFilterByIdx[]).reduce(\n (res, item) => {\n res[item[0]] = item[1];\n return res;\n },\n axesToBeFixed,\n ),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\n/** Identity comparison for PlAdvancedFilterColumnId values — handles both string\n * and object (PTableColumnId) variants. */\nfunction columnIdEquals(a: PlAdvancedFilterColumnId, b: PlAdvancedFilterColumnId): boolean {\n if (a === b) return true;\n if (typeof a === \"object\" && typeof b === \"object\" && a !== null && b !== null) {\n return canonicalizeJson(a) === canonicalizeJson(b);\n }\n return false;\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n // PTableColumnId object form can't carry axisFilters — return as-is.\n if (isPTableColumnIdObject(value.source)) return value.source;\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as unknown as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => columnIdEquals(op.id, columnAsSourceAndFixedAxes.value.source)),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"FilterEditor.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n PTableColumnId,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n canonicalizeJson,\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) =>\n columnIdEquals(v.id, getSourceId(newSourceId)),\n );\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find((op) =>\n columnIdEquals(op.id, getSourceId(props.filter.column)),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction isPTableColumnIdObject(c: PlAdvancedFilterColumnId): c is PTableColumnId {\n return typeof c === \"object\" && c !== null;\n}\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n // PTableColumnId object form: no axisFilters embedded, return as-is.\n if (isPTableColumnIdObject(column)) return column;\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => columnIdEquals(op.id, sourceId));\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n // PTableColumnId object form has no axis filters baked in.\n if (isPTableColumnIdObject(column)) {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: (parsedColumnId.axisFilters as AxisFilterByIdx[]).reduce(\n (res, item) => {\n res[item[0]] = item[1];\n return res;\n },\n axesToBeFixed,\n ),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\n/** Identity comparison for PlAdvancedFilterColumnId values — handles both string\n * and object (PTableColumnId) variants. */\nfunction columnIdEquals(a: PlAdvancedFilterColumnId, b: PlAdvancedFilterColumnId): boolean {\n if (a === b) return true;\n if (typeof a === \"object\" && typeof b === \"object\" && a !== null && b !== null) {\n return canonicalizeJson(a) === canonicalizeJson(b);\n }\n return false;\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n // PTableColumnId object form can't carry axisFilters — return as-is.\n if (isPTableColumnIdObject(value.source)) return value.source;\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as unknown as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => columnIdEquals(op.id, columnAsSourceAndFixedAxes.value.source)),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div data-testid=\"pl-advanced-filter-filter\" :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n data-testid=\"pl-advanced-filter-column-chip\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div\n data-testid=\"pl-advanced-filter-column-title\"\n :class=\"$style.titleWrapper\"\n :title=\"currentOption?.label ?? ''\"\n >\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div\n data-testid=\"pl-advanced-filter-remove\"\n :class=\"$style.closeIcon\"\n @click=\"onDelete(props.filter.column)\"\n >\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n data-testid=\"pl-advanced-filter-column\"\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div\n data-testid=\"pl-advanced-filter-remove\"\n :class=\"$style.closeButton\"\n @click=\"onDelete(props.filter.column)\"\n >\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div\n v-if=\"currentOption?.axesToBeFixed?.length\"\n data-testid=\"pl-advanced-filter-fixed-axes\"\n :class=\"$style.fixedAxesBlock\"\n >\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n data-testid=\"pl-advanced-filter-fixed-axis\"\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n data-testid=\"pl-advanced-filter-type\"\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div data-testid=\"pl-advanced-filter-fuzzy-settings\" :class=\"$style.innerSection\">\n <Slider\n data-testid=\"pl-advanced-filter-max-edits\"\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n data-testid=\"pl-advanced-filter-substitutions-only\"\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n data-testid=\"pl-advanced-filter-wildcard\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEditor.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n PTableColumnId,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n canonicalizeJson,\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) =>\n columnIdEquals(v.id, getSourceId(newSourceId)),\n );\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find((op) =>\n columnIdEquals(op.id, getSourceId(props.filter.column)),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction isPTableColumnIdObject(c: PlAdvancedFilterColumnId): c is PTableColumnId {\n return typeof c === \"object\" && c !== null;\n}\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n // PTableColumnId object form: no axisFilters embedded, return as-is.\n if (isPTableColumnIdObject(column)) return column;\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => columnIdEquals(op.id, sourceId));\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n // PTableColumnId object form has no axis filters baked in.\n if (isPTableColumnIdObject(column)) {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: (parsedColumnId.axisFilters as AxisFilterByIdx[]).reduce(\n (res, item) => {\n res[item[0]] = item[1];\n return res;\n },\n axesToBeFixed,\n ),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\n/** Identity comparison for PlAdvancedFilterColumnId values — handles both string\n * and object (PTableColumnId) variants. */\nfunction columnIdEquals(a: PlAdvancedFilterColumnId, b: PlAdvancedFilterColumnId): boolean {\n if (a === b) return true;\n if (typeof a === \"object\" && typeof b === \"object\" && a !== null && b !== null) {\n return canonicalizeJson(a) === canonicalizeJson(b);\n }\n return false;\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n // PTableColumnId object form can't carry axisFilters — return as-is.\n if (isPTableColumnIdObject(value.source)) return value.source;\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as unknown as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => columnIdEquals(op.id, columnAsSourceAndFixedAxes.value.source)),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"FilterEditor.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n PTableColumnId,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n canonicalizeJson,\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) =>\n columnIdEquals(v.id, getSourceId(newSourceId)),\n );\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find((op) =>\n columnIdEquals(op.id, getSourceId(props.filter.column)),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction isPTableColumnIdObject(c: PlAdvancedFilterColumnId): c is PTableColumnId {\n return typeof c === \"object\" && c !== null;\n}\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n // PTableColumnId object form: no axisFilters embedded, return as-is.\n if (isPTableColumnIdObject(column)) return column;\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => columnIdEquals(op.id, sourceId));\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n // PTableColumnId object form has no axis filters baked in.\n if (isPTableColumnIdObject(column)) {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: (parsedColumnId.axisFilters as AxisFilterByIdx[]).reduce(\n (res, item) => {\n res[item[0]] = item[1];\n return res;\n },\n axesToBeFixed,\n ),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\n/** Identity comparison for PlAdvancedFilterColumnId values — handles both string\n * and object (PTableColumnId) variants. */\nfunction columnIdEquals(a: PlAdvancedFilterColumnId, b: PlAdvancedFilterColumnId): boolean {\n if (a === b) return true;\n if (typeof a === \"object\" && typeof b === \"object\" && a !== null && b !== null) {\n return canonicalizeJson(a) === canonicalizeJson(b);\n }\n return false;\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n // PTableColumnId object form can't carry axisFilters — return as-is.\n if (isPTableColumnIdObject(value.source)) return value.source;\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as unknown as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => columnIdEquals(op.id, columnAsSourceAndFixedAxes.value.source)),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div data-testid=\"pl-advanced-filter-filter\" :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n data-testid=\"pl-advanced-filter-column-chip\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div\n data-testid=\"pl-advanced-filter-column-title\"\n :class=\"$style.titleWrapper\"\n :title=\"currentOption?.label ?? ''\"\n >\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div\n data-testid=\"pl-advanced-filter-remove\"\n :class=\"$style.closeIcon\"\n @click=\"onDelete(props.filter.column)\"\n >\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n data-testid=\"pl-advanced-filter-column\"\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div\n data-testid=\"pl-advanced-filter-remove\"\n :class=\"$style.closeButton\"\n @click=\"onDelete(props.filter.column)\"\n >\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div\n v-if=\"currentOption?.axesToBeFixed?.length\"\n data-testid=\"pl-advanced-filter-fixed-axes\"\n :class=\"$style.fixedAxesBlock\"\n >\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n data-testid=\"pl-advanced-filter-fixed-axis\"\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n data-testid=\"pl-advanced-filter-type\"\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div data-testid=\"pl-advanced-filter-fuzzy-settings\" :class=\"$style.innerSection\">\n <Slider\n data-testid=\"pl-advanced-filter-max-edits\"\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n data-testid=\"pl-advanced-filter-substitutions-only\"\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n data-testid=\"pl-advanced-filter-wildcard\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEditor.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"names":[],"mappings":"AA+jBA,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAUnG,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,gBAAgB,EAAE,OAAO,sBAAsB,CAAC;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE;QAC1B,QAAQ,EAAE,wBAAwB,CAAC;QACnC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACrF,QAAQ,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,eAAe,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,CAAC;AAmxBF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"FilterEditor.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"names":[],"mappings":"AA6lBA,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAUnG,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,gBAAgB,EAAE,OAAO,sBAAsB,CAAC;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE;QAC1B,QAAQ,EAAE,wBAAwB,CAAC;QACnC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACrF,QAAQ,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,eAAe,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,CAAC;AAozBF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -149,9 +149,13 @@ var P = ["title"], F = /* @__PURE__ */ h({
149
149
  return !0;
150
150
  }
151
151
  });
152
- return (e, t) => (_(), f(c, null, [p("div", { class: g(e.$style.filterWrapper) }, [
152
+ return (e, t) => (_(), f(c, null, [p("div", {
153
+ "data-testid": "pl-advanced-filter-filter",
154
+ class: g(e.$style.filterWrapper)
155
+ }, [
153
156
  h.enableDnd ? (_(), f("div", {
154
157
  key: 0,
158
+ "data-testid": "pl-advanced-filter-column-chip",
155
159
  class: g([
156
160
  e.$style.top,
157
161
  e.$style.columnChip,
@@ -166,10 +170,12 @@ var P = ["title"], F = /* @__PURE__ */ h({
166
170
  name: Q.value === "String" || Q.value === void 0 ? "cell-type-txt" : "cell-type-num"
167
171
  }, null, 8, ["name"]))], 2),
168
172
  p("div", {
173
+ "data-testid": "pl-advanced-filter-column-title",
169
174
  class: g(e.$style.titleWrapper),
170
175
  title: X.value?.label ?? ""
171
176
  }, [p("div", { class: g(e.$style.title) }, y(V.value ? "Inconsistent value" : X.value?.label ?? F.filter.column), 3)], 10, P),
172
177
  p("div", {
178
+ "data-testid": "pl-advanced-filter-remove",
173
179
  class: g(e.$style.closeIcon),
174
180
  onClick: t[0] ||= (e) => h.onDelete(F.filter.column)
175
181
  }, [m(b(k), { name: "close" })], 2)
@@ -177,6 +183,7 @@ var P = ["title"], F = /* @__PURE__ */ h({
177
183
  key: 1,
178
184
  class: g(e.$style.top)
179
185
  }, [m(b(O), {
186
+ "data-testid": "pl-advanced-filter-column",
180
187
  "model-value": J.value.source,
181
188
  errorStatus: $.value,
182
189
  options: H.value,
@@ -188,14 +195,17 @@ var P = ["title"], F = /* @__PURE__ */ h({
188
195
  "errorStatus",
189
196
  "options"
190
197
  ]), p("div", {
198
+ "data-testid": "pl-advanced-filter-remove",
191
199
  class: g(e.$style.closeButton),
192
200
  onClick: t[1] ||= (e) => h.onDelete(F.filter.column)
193
201
  }, [m(b(k), { name: "close" })], 2)], 2)),
194
202
  X.value?.axesToBeFixed?.length ? (_(), f("div", {
195
203
  key: 2,
204
+ "data-testid": "pl-advanced-filter-fixed-axes",
196
205
  class: g(e.$style.fixedAxesBlock)
197
206
  }, [(_(!0), f(c, null, v(X.value?.axesToBeFixed, (e) => (_(), u(b(E), {
198
207
  key: e.idx,
208
+ "data-testid": "pl-advanced-filter-fixed-axis",
199
209
  "model-value": J.value.axisFiltersByIndex[e.idx],
200
210
  label: e.label,
201
211
  "options-search": (t, n) => L(J.value.source, n, t, e.idx),
@@ -210,6 +220,7 @@ var P = ["title"], F = /* @__PURE__ */ h({
210
220
  "onUpdate:modelValue"
211
221
  ]))), 128))], 2)) : d("", !0),
212
222
  p("div", { class: g(F.filter.type === "isNA" || F.filter.type === "isNotNA" ? e.$style.bottom : e.$style.middle) }, [m(b(O), {
223
+ "data-testid": "pl-advanced-filter-type",
213
224
  "model-value": F.filter.type,
214
225
  options: ee.value,
215
226
  "group-position": F.filter.type === "isNA" || F.filter.type === "isNotNA" ? "bottom" : "middle",
@@ -220,17 +231,23 @@ var P = ["title"], F = /* @__PURE__ */ h({
220
231
  "group-position"
221
232
  ])], 2),
222
233
  F.filter.type === "patternFuzzyContainSubsequence" ? (_(), f(c, { key: 3 }, [p("div", { class: g(e.$style.middle) }, [m(b(j), {
234
+ "data-testid": "pl-advanced-filter-value",
223
235
  "model-value": F.filter.value,
224
236
  placeholder: "Substring",
225
237
  "group-position": "middle",
226
238
  "onUpdate:modelValue": t[2] ||= (e) => I("value", e)
227
- }, null, 8, ["model-value"])], 2), p("div", { class: g(e.$style.innerSection) }, [m(b(N), {
239
+ }, null, 8, ["model-value"])], 2), p("div", {
240
+ "data-testid": "pl-advanced-filter-fuzzy-settings",
241
+ class: g(e.$style.innerSection)
242
+ }, [m(b(N), {
243
+ "data-testid": "pl-advanced-filter-max-edits",
228
244
  "model-value": F.filter.maxEdits,
229
245
  max: 5,
230
246
  breakpoints: "",
231
247
  label: "Maximum number of substitutions and indels",
232
248
  "onUpdate:modelValue": t[3] ||= (e) => I("maxEdits", e)
233
249
  }, null, 8, ["model-value"]), m(b(M), {
250
+ "data-testid": "pl-advanced-filter-substitutions-only",
234
251
  "model-value": F.filter.substitutionsOnly,
235
252
  label: "Substitutions only",
236
253
  "onUpdate:modelValue": t[4] ||= (e) => I("substitutionsOnly", e)
@@ -238,6 +255,7 @@ var P = ["title"], F = /* @__PURE__ */ h({
238
255
  p("div", { class: g(e.$style.bottom) }, [
239
256
  F.filter.type === "patternEquals" || F.filter.type === "patternNotEquals" ? (_(), u(b(E), {
240
257
  key: 0,
258
+ "data-testid": "pl-advanced-filter-value",
241
259
  "model-value": F.filter.value,
242
260
  "options-search": (e, t) => L(J.value.source, t, e),
243
261
  clearable: !0,
@@ -246,6 +264,7 @@ var P = ["title"], F = /* @__PURE__ */ h({
246
264
  }, null, 8, ["model-value", "options-search"])) : d("", !0),
247
265
  F.filter.type === "inSet" || F.filter.type === "notInSet" ? (_(), u(b(D), {
248
266
  key: 1,
267
+ "data-testid": "pl-advanced-filter-value",
249
268
  "model-value": F.filter.value,
250
269
  "options-search": (e, t) => R(J.value.source, t, e),
251
270
  disabled: V.value,
@@ -258,18 +277,21 @@ var P = ["title"], F = /* @__PURE__ */ h({
258
277
  ])) : d("", !0),
259
278
  b(a)(F.filter) ? (_(), u(b(A), {
260
279
  key: 2,
280
+ "data-testid": "pl-advanced-filter-value",
261
281
  "model-value": F.filter.x,
262
282
  "group-position": "bottom",
263
283
  "onUpdate:modelValue": t[7] ||= (e) => I("x", e)
264
284
  }, null, 8, ["model-value"])) : d("", !0),
265
285
  b(o)(F.filter) ? (_(), u(b(A), {
266
286
  key: 3,
287
+ "data-testid": "pl-advanced-filter-value",
267
288
  "model-value": F.filter.n,
268
289
  "group-position": "bottom",
269
290
  "onUpdate:modelValue": t[8] ||= (e) => I("n", e)
270
291
  }, null, 8, ["model-value"])) : d("", !0),
271
292
  F.filter.type === "patternContainSubsequence" || F.filter.type === "patternNotContainSubsequence" ? (_(), u(b(j), {
272
293
  key: 4,
294
+ "data-testid": "pl-advanced-filter-value",
273
295
  "model-value": F.filter.value,
274
296
  placeholder: "Substring",
275
297
  "group-position": "bottom",
@@ -277,6 +299,7 @@ var P = ["title"], F = /* @__PURE__ */ h({
277
299
  }, null, 8, ["model-value"])) : d("", !0),
278
300
  F.filter.type === "patternMatchesRegularExpression" ? (_(), u(b(j), {
279
301
  key: 5,
302
+ "data-testid": "pl-advanced-filter-value",
280
303
  "model-value": F.filter.value,
281
304
  error: ne.value ? "Regular expression is not valid" : void 0,
282
305
  placeholder: "Regular expression",
@@ -285,6 +308,7 @@ var P = ["title"], F = /* @__PURE__ */ h({
285
308
  }, null, 8, ["model-value", "error"])) : d("", !0),
286
309
  F.filter.type === "patternFuzzyContainSubsequence" ? (_(), u(b(O), {
287
310
  key: 6,
311
+ "data-testid": "pl-advanced-filter-wildcard",
288
312
  "model-value": F.filter.wildcard,
289
313
  clearable: "",
290
314
  placeholder: "Wildcard value",
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEditor.vue_vue_type_script_setup_true_lang.js","names":["$style"],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n PTableColumnId,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n canonicalizeJson,\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) =>\n columnIdEquals(v.id, getSourceId(newSourceId)),\n );\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find((op) =>\n columnIdEquals(op.id, getSourceId(props.filter.column)),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction isPTableColumnIdObject(c: PlAdvancedFilterColumnId): c is PTableColumnId {\n return typeof c === \"object\" && c !== null;\n}\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n // PTableColumnId object form: no axisFilters embedded, return as-is.\n if (isPTableColumnIdObject(column)) return column;\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => columnIdEquals(op.id, sourceId));\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n // PTableColumnId object form has no axis filters baked in.\n if (isPTableColumnIdObject(column)) {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: (parsedColumnId.axisFilters as AxisFilterByIdx[]).reduce(\n (res, item) => {\n res[item[0]] = item[1];\n return res;\n },\n axesToBeFixed,\n ),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\n/** Identity comparison for PlAdvancedFilterColumnId values — handles both string\n * and object (PTableColumnId) variants. */\nfunction columnIdEquals(a: PlAdvancedFilterColumnId, b: PlAdvancedFilterColumnId): boolean {\n if (a === b) return true;\n if (typeof a === \"object\" && typeof b === \"object\" && a !== null && b !== null) {\n return canonicalizeJson(a) === canonicalizeJson(b);\n }\n return false;\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n // PTableColumnId object form can't carry axisFilters — return as-is.\n if (isPTableColumnIdObject(value.source)) return value.source;\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as unknown as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => columnIdEquals(op.id, columnAsSourceAndFixedAxes.value.source)),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div :class=\"$style.titleWrapper\" :title=\"currentOption?.label ?? ''\">\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div :class=\"$style.closeIcon\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div :class=\"$style.closeButton\" @click=\"onDelete(props.filter.column)\">\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div v-if=\"currentOption?.axesToBeFixed?.length\" :class=\"$style.fixedAxesBlock\">\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div :class=\"$style.innerSection\">\n <Slider\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;EAuCA,IAAM,IAAQ;EAqBd,SAAS,EACP,GACA,GACA;AACA,KAAM,eAAe;IAAE,GAAG,EAAM;KAAS,IAAM;IAAG,CAAmB;;EAGvE,eAAe,EACb,GACA,GACA,GACA,GACmC;AACnC,UAAO,EAAM,kBAAkB;IAC7B,UAAU;IACV;IACA,YAAY;IACZ,WAAW;IACZ,CAAC;;EAGJ,eAAe,EACb,GACA,GACA,GACA,GACmC;AACnC,OAAI,MAAS,WAAW,OAAO,KAAQ,SACrC,QAAO,EAAoB,GAAI,GAAM,GAAK,EAAQ;AAEpD,OAAI,MAAS,WAAW,MAAM,QAAQ,EAAI,CAExC,SADgB,MAAM,QAAQ,IAAI,EAAI,KAAK,MAAM,EAAoB,GAAI,GAAM,GAAG,EAAQ,CAAC,CAAC,EAC7E,KAAK,MAAM,EAAE,GAAG;AAEjC,SAAU,MAAM,gCAAgC;;EAGlD,SAAS,EAAiB,GAAkC;AACtD,KAAM,EAAQ,IAClB,EAAM,eAAe,EAAyB,EAAM,QAAQ,EAAQ,CAAC;;EAGvE,SAAS,EAAe,GAAwC;AAC9D,OAAI,CAAC,EACH;GAEF,IAAM,IAAgB,EAAM,cAAc,MAAM,MAC9C,EAAe,EAAE,IAAI,EAAY,EAAY,CAAC,CAC/C;AACD,OAAI,CAAC,EACH;GAEF,IAAM,IAAa,EAAc,EAAM,OAAO,KAAK,EAC7C,IAAgB,EAAkB,GAAe,KAAK;AAC5D,GAAI,EAAW,aAAa,EAAc,GACxC,EAAM,eAAe;IAAE,GAAG,EAAM;IAAQ,QAAQ;IAAa,CAAC,GAE9D,EAAM,eAAe;IACnB,GAAG,EAAgB;IACnB,QAAQ;IACT,CAAC;;EAIN,IAAM,IAA6B,QACV,EAAM,cAAc,MAAM,MAC/C,EAAe,EAAG,IAAI,EAAY,EAAM,OAAO,OAAO,CAAC,CACxD,KACyB,KAAA,EAC1B,EACI,IAAgB,QAAe;GACnC,IAAM,IAAU,EAAM,cAAc,KAAK,OAAO;IAAE,OAAO,EAAE;IAAI,OAAO,EAAE,SAAS;IAAG,EAAE;AAItF,UAHI,EAA2B,SAC7B,EAAQ,QAAQ;IAAE,OAAO,EAAM,OAAO;IAAQ,OAAO;IAAsB,CAAC,EAEvE;IACP;EAEF,SAAS,EAAuB,GAAkD;AAChF,UAAO,OAAO,KAAM,cAAY;;EAGlC,SAAS,EAAY,GAA4D;AAE/E,OAAI,EAAuB,EAAO,CAAE,QAAO;AAC3C,OAAI;IACF,IAAM,IAAiB,EAAc,EAA8B;AAIjE,WAHE,EAAkB,EAAe,GAC5B,EAAkB,EAAe,OAAO,GAExC;WAEH;AACN,WAAO;;;EASX,SAAS,EACP,GAC4B;GAC5B,IAAM,IAAW,EAAY,EAAO,EAE9B,KADS,EAAM,cAAc,MAAM,MAAO,EAAe,EAAG,IAAI,EAAS,CAAC,EACjD,iBAAiB,EAAE,EAAE,QACjD,GAAK,OACJ,EAAI,EAAK,OAAO,KAAA,GACT,IAET,EAAE,CACH;AAED,OAAI,EAAuB,EAAO,CAChC,QAAO;IAAE,QAAQ;IAAQ,oBAAoB;IAAe;AAE9D,OAAI;IACF,IAAM,IAAiB,EAAc,EAA8B;AACnE,QAAI,EAAkB,EAAe,CACnC,QAAO;KACL,QAAQ;KACR,oBAAqB,EAAe,YAAkC,QACnE,GAAK,OACJ,EAAI,EAAK,MAAM,EAAK,IACb,IAET,EACD;KACF;WAEG;AACN,WAAO;KAAE,QAAQ;KAAQ,oBAAoB;KAAe;;AAE9D,UAAO;IAAE,QAAQ;IAAQ,oBAAoB;IAAe;;EAK9D,SAAS,EAAe,GAA6B,GAAsC;AAKzF,UAJI,MAAM,IAAU,KAChB,OAAO,KAAM,YAAY,OAAO,KAAM,YAAY,MAAM,QAAQ,MAAM,OACjE,EAAiB,EAAE,KAAK,EAAiB,EAAE,GAE7C;;EAGT,SAAS,EAAgB,GAA6D;AAMpF,UALI,OAAO,KAAK,EAAM,mBAAmB,CAAC,WAAW,KAIjD,EAAuB,EAAM,OAAO,GAAS,EAAM,SAChD,EAAkB;IACvB,QAAQ,EAAc,EAAM,OAA8B;IAC1D,aAAa,OAAO,QAAQ,EAAM,mBAAmB,CAAC,KACnD,CAAC,GAAK,OAAW,CAAC,OAAO,EAAI,EAAE,EAAM,CACvC;IACF,CAAC;;EAGJ,IAAM,IAA6B,EAAS;GAC1C,WACS,EAA8B,EAAM,OAAO,OAAO;GAE3D,MAAM,MAAU;AACd,MAAM,eAAe;KAAE,GAAG,EAAM;KAAQ,QAAQ,EAAgB,EAAM;KAAE,CAAC;;GAE5E,CAAC;EACF,SAAS,EAAsB,GAAa,GAAoC;AAC9E,KAA2B,QAAQ;IACjC,GAAG,EAA2B;IAC9B,oBAAoB;KAAE,GAAG,EAA2B,MAAM;MAAqB,IAAM;KAAO;IAC7F;;EAGH,IAAM,IAAgB,QACpB,EAAM,cAAc,MAAM,MAAO,EAAe,EAAG,IAAI,EAA2B,MAAM,OAAO,CAAC,CACjG,EACK,IAAc,QAClB,EAAc,OAAO,OAAO,EAAkB,EAAc,MAAM,KAAK,GAAG,KAC3E,EACK,IAAc,QAAe,EAAY,OAAO,UAAU,EAC1D,IAAe,QACb,EAAQ,EAAc,OAAO,SAAU,EAA2B,MACzE,EAEK,KAAqB,QACzB,EAAM,iBACH,QACE,MACC,EAAM,OAAO,SAAS,MACrB,EAAY,QAAQ,EAAc,EAAE,CAAC,aAAa,EAAY,MAAM,GAAG,IAC5E,CACC,KAAK,OAAO;GAAE,OAAO;GAAG,OAAO,EAAc,EAAE,CAAC;GAAO,EAAE,CAC7D,EAEK,KAAkB,QAClB,EAAM,OAAO,SAAS,mCAGtB,EAAc,OAAO,aAAa,eAC7B,CAAC;GAAE,OAAO;GAAK,OAAO;GAAK,CAAC,GAEjC,EAAc,OAAO,aAAa,cAC7B,CAAC;GAAE,OAAO;GAAK,OAAO;GAAK,CAAC,GAE9B,CAAC,GAAG,IAAI,IAAI,EAAM,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO;GAAE,OAAO;GAAG,OAAO;GAAG,EAAE,GARpF,EAAE,CASX,EAEI,KAAqB,QAAe;AACxC,OAAI,EAAM,OAAO,SAAS,kCACxB,QAAO;AAET,OAAI;AAEF,WADA,IAAI,OAAO,EAAM,OAAO,MAAM,EACvB;WACD;AACN,WAAO;;IAET;qCAGA,EAsKM,OAAA,EAtKA,OAAK,EAAEA,EAAAA,OAAO,cAAa,EAAA,EAAA;GAGvB,EAAA,aAAA,GAAA,EADR,EA2BM,OAAA;;IAzBH,OAAK,EAAA;KAAGA,EAAAA,OAAO;KAAKA,EAAAA,OAAO;KAAU,GAAKA,EAAAA,OAAO,QAAQ,EAAA,OAAY;KAAA,CAAA;;IAEtE,EAUM,OAAA,EAVA,OAAK,EAAA,CAAGA,EAAAA,OAAO,UAAQ,GAAKA,EAAAA,OAAO,QAAQ,EAAA,OAAY,CAAA,CAAA,EAAA,EAAA,CAC3C,EAAA,SAAA,GAAA,EAAhB,EAA+C,EAAA,EAAA,EAAA;;KAAjB,MAAK;gBACnC,EAOE,EAAA,EAAA,EAAA;;KALC,MAAmB,EAAA,UAAW,YAAiB,EAAA,UAAgB,KAAA,IAAA,kBAAA;;IAOpE,EAQM,OAAA;KARA,OAAK,EAAEA,EAAAA,OAAO,aAAY;KAAG,OAAO,EAAA,OAAe,SAAK;QAC5D,EAMM,OAAA,EANA,OAAK,EAAEA,EAAAA,OAAO,MAAK,EAAA,EAAA,EAErB,EAAA,QAAA,uBAAiF,EAAA,OAAe,SAAS,EAAM,OAAO,OAAM,EAAA,EAAA,CAAA,EAAA,IAAA,EAAA;IAMlI,EAEM,OAAA;KAFA,OAAK,EAAEA,EAAAA,OAAO,UAAS;KAAG,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,SAAS,EAAM,OAAO,OAAM;QACjE,EAAyB,EAAA,EAAA,EAAA,EAAf,MAAK,SAAO,CAAA,CAAA,EAAA,EAAA;kBAG1B,EAYM,OAAA;;IAZO,OAAK,EAAEA,EAAAA,OAAO,IAAG;OAC5B,EAOE,EAAA,EAAA,EAAA;IANC,eAAa,EAAA,MAA2B;IACxC,aAAa,EAAA;IACb,SAAS,EAAA;IACT,OAAO,EAAA,OAAA,QAAiB;IACzB,kBAAe;IACd,uBAAoB;;;;;OAEvB,EAEM,OAAA;IAFA,OAAK,EAAEA,EAAAA,OAAO,YAAW;IAAG,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,SAAS,EAAM,OAAO,OAAM;OACnE,EAAyB,EAAA,EAAA,EAAA,EAAf,MAAK,SAAO,CAAA,CAAA,EAAA,EAAA,CAAA,EAAA,EAAA;GAIf,EAAA,OAAe,eAAe,UAAA,GAAA,EAAzC,EAcM,OAAA;;IAd4C,OAAK,EAAEA,EAAAA,OAAO,eAAc;eAC5E,EAYW,GAAA,MAAA,EAZe,EAAA,OAAe,gBAAxB,YACf,EAUE,EAAA,EAAA,EAAA;SAX0D,EAAM;IAE/D,eAAa,EAAA,MAA2B,mBAAmB,EAAM;IACjE,OAAO,EAAM;IACb,mBAA8B,GAAK,MAAuB,EAAoB,EAAA,MAA2B,QAAQ,GAAM,GAAK,EAAM,IAAG;IAIrI,UAAU,EAAA;IACV,WAAW;IACX,wBAAqB,MAAM,EAAsB,EAAM,KAAK,EAAC;;;;;;;;GAMpE,EAeM,OAAA,EAdH,OAAK,EAAW,EAAM,OAAO,SAAI,UAAe,EAAM,OAAO,SAAI,YAA2BA,EAAAA,OAAO,SAAmBA,EAAAA,OAAO,OAAA,EAAA,EAAA,CAM9H,EAOE,EAAA,EAAA,EAAA;IANC,eAAa,EAAM,OAAO;IAC1B,SAAS,GAAA;IACT,kBAA2B,EAAM,OAAO,SAAI,UAAe,EAAM,OAAO,SAAI,YAAA,WAAA;IAG5E,uBAAoB;;;;;;GAKT,EAAM,OAAO,SAAI,oCAAA,GAAA,EAAjC,EAuBW,GAAA,EAAA,KAAA,GAAA,EAAA,CAtBT,EAOM,OAAA,EAPA,OAAK,EAAEA,EAAAA,OAAO,OAAM,EAAA,EAAA,CACxB,EAKE,EAAA,EAAA,EAAA;IAJC,eAAa,EAAM,OAAO;IAC3B,aAAY;IACZ,kBAAe;IACd,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,SAAU,EAAC;sCAG3D,EAaM,OAAA,EAbA,OAAK,EAAEA,EAAAA,OAAO,aAAY,EAAA,EAAA,CAC9B,EAME,EAAA,EAAA,EAAA;IALC,eAAa,EAAM,OAAO;IAC1B,KAAK;IACN,aAAA;IACA,OAAM;IACL,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,YAAa,EAAC;iCAE5D,EAIE,EAAA,EAAA,EAAA;IAHC,eAAa,EAAM,OAAO;IAC3B,OAAM;IACL,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,qBAAsB,EAAC;;GAMzE,EA4DM,OAAA,EA5DA,OAAK,EAAEA,EAAAA,OAAO,OAAM,EAAA,EAAA;IAEhB,EAAM,OAAO,SAAI,mBAAwB,EAAM,OAAO,SAAI,sBAAA,GAAA,EADlE,EASE,EAAA,EAAA,EAAA;;KAPC,eAAa,EAAM,OAAO;KAC1B,mBAA4B,GAAK,MAAS,EAAoB,EAAA,MAA2B,QAAQ,GAAM,EAAG;KAG1G,WAAW;KACZ,kBAAe;KACd,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,SAAU,EAAC;;IAGjD,EAAM,OAAO,SAAI,WAAgB,EAAM,OAAO,SAAI,cAAA,GAAA,EAD1D,EASE,EAAA,EAAA,EAAA;;KAPC,eAAa,EAAM,OAAO;KAC1B,mBAA4B,GAAK,MAAS,EAAyB,EAAA,MAA2B,QAAQ,GAAM,EAAG;KAG/G,UAAU,EAAA;KACX,kBAAe;KACd,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,SAAU,EAAC;;;;;;IAGjD,EAAA,EAAe,CAAC,EAAM,OAAM,IAAA,GAAA,EADpC,EAKE,EAAA,EAAA,EAAA;;KAHC,eAAa,EAAM,OAAO;KAC3B,kBAAe;KACd,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,KAAM,EAAC;;IAG7C,EAAA,EAAgB,CAAC,EAAM,OAAM,IAAA,GAAA,EADrC,EAKE,EAAA,EAAA,EAAA;;KAHC,eAAa,EAAM,OAAO;KAC3B,kBAAe;KACd,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,KAAM,EAAC;;IAGlC,EAAM,OAAO,SAAI,+BAA8C,EAAM,OAAO,SAAI,kCAAA,GAAA,EADnG,EASE,EAAA,EAAA,EAAA;;KAJC,eAAa,EAAM,OAAO;KAC3B,aAAY;KACZ,kBAAe;KACd,uBAAkB,AAAA,EAAA,QAAG,MAAM,EAAgB,SAAU,EAAC;;IAGjD,EAAM,OAAO,SAAI,qCAAA,GAAA,EADzB,EAOE,EAAA,EAAA,EAAA;;KALC,eAAa,EAAM,OAAO;KAC1B,OAAO,GAAA,QAAkB,oCAAuC,KAAA;KACjE,aAAY;KACZ,kBAAe;KACd,uBAAkB,AAAA,EAAA,SAAG,MAAM,EAAgB,SAAU,EAAC;;IAGjD,EAAM,OAAO,SAAI,oCAAA,GAAA,EADzB,EAQE,EAAA,EAAA,EAAA;;KANC,eAAa,EAAM,OAAO;KAC3B,WAAA;KACA,aAAY;KACX,SAAS,GAAA;KACV,kBAAe;KACd,uBAAkB,AAAA,EAAA,SAAG,MAAM,EAAgB,YAAa,EAAC;;;SAIhE,EAAgF,GAAA;GAAhE,QAAQ,EAAA;GAAU,UAAU,EAAA;GAAS,UAAQ,EAAA"}
1
+ {"version":3,"file":"FilterEditor.vue_vue_type_script_setup_true_lang.js","names":["$style"],"sources":["../../../src/components/PlAdvancedFilter/FilterEditor.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport {\n PlAutocomplete,\n PlAutocompleteMulti,\n PlDropdown,\n PlIcon16,\n PlNumberField,\n PlTextField,\n PlToggleSwitch,\n Slider,\n} from \"@milaboratories/uikit\";\nimport type {\n AnchoredPColumnId,\n AxisFilterByIdx,\n AxisFilterValue,\n PTableColumnId,\n SUniversalPColumnId,\n} from \"@platforma-sdk/model\";\nimport {\n canonicalizeJson,\n isFilteredPColumn,\n parseColumnId,\n stringifyColumnId,\n type ListOptionBase,\n} from \"@platforma-sdk/model\";\nimport { computed } from \"vue\";\nimport type { SUPPORTED_FILTER_TYPES } from \"./constants\";\nimport { DEFAULT_FILTER_TYPE, DEFAULT_FILTERS } from \"./constants\";\nimport OperandButton from \"./OperandButton.vue\";\nimport type { EditableFilter, Operand, PlAdvancedFilterColumnId, SourceOptionInfo } from \"./types\";\nimport {\n getFilterInfo,\n getNormalizedSpec,\n isNumericFilter,\n isPositionFilter,\n mergeFilterForTypeChange,\n} from \"./utils\";\nimport { isNil } from \"es-toolkit\";\n\nconst props = defineProps<{\n filter: EditableFilter;\n isLast: boolean;\n operand: Operand;\n enableDnd: boolean;\n columnOptions: SourceOptionInfo[];\n supportedFilters: typeof SUPPORTED_FILTER_TYPES;\n getSuggestOptions: (params: {\n columnId: PlAdvancedFilterColumnId;\n axisIdx?: number;\n searchType: \"value\" | \"label\";\n searchStr: string;\n }) => ListOptionBase<string | number>[] | Promise<ListOptionBase<string | number>[]>;\n onDelete: (columnId: PlAdvancedFilterColumnId) => void;\n onUpdateFilter: (filter: EditableFilter) => void;\n onChangeOperand: (op: Operand) => void;\n}>();\n\ntype AllKeys<U> = U extends unknown ? keyof U : never;\ntype ValueOf<U, K extends string> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;\n\nfunction updateFilterProp<K extends AllKeys<EditableFilter> & string>(\n key: K,\n v: ValueOf<EditableFilter, K>,\n) {\n props.onUpdateFilter({ ...props.filter, [key]: v } as EditableFilter);\n}\n\nasync function getSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string,\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n return props.getSuggestOptions({\n columnId: id,\n axisIdx,\n searchType: type,\n searchStr: str,\n }) as Promise<ListOptionBase<string>[]>;\n}\n\nasync function getMultiSuggestOptionsFn(\n id: PlAdvancedFilterColumnId,\n type: \"value\" | \"label\",\n str: string | string[],\n axisIdx?: number,\n): Promise<ListOptionBase<string>[]> {\n if (type === \"label\" && typeof str === \"string\") {\n return getSuggestOptionsFn(id, type, str, axisIdx);\n }\n if (type === \"value\" && Array.isArray(str)) {\n const results = await Promise.all(str.map((s) => getSuggestOptionsFn(id, type, s, axisIdx)));\n return results.map((x) => x[0]);\n }\n throw new Error(\"Invalid arguments combination\");\n}\n\nfunction changeFilterType(newType?: EditableFilter[\"type\"]) {\n if (isNil(newType)) return;\n props.onUpdateFilter(mergeFilterForTypeChange(props.filter, newType));\n}\n\nfunction changeSourceId(newSourceId?: PlAdvancedFilterColumnId) {\n if (!newSourceId) {\n return;\n }\n const newSourceInfo = props.columnOptions.find((v) =>\n columnIdEquals(v.id, getSourceId(newSourceId)),\n );\n if (!newSourceInfo) {\n return;\n }\n const filterInfo = getFilterInfo(props.filter.type);\n const newSourceSpec = getNormalizedSpec(newSourceInfo?.spec);\n if (filterInfo.supportedFor(newSourceSpec)) {\n props.onUpdateFilter({ ...props.filter, column: newSourceId });\n } else {\n props.onUpdateFilter({\n ...DEFAULT_FILTERS[DEFAULT_FILTER_TYPE],\n column: newSourceId,\n });\n }\n}\n\nconst inconsistentSourceSelected = computed(() => {\n const selectedOption = props.columnOptions.find((op) =>\n columnIdEquals(op.id, getSourceId(props.filter.column)),\n );\n return selectedOption === undefined;\n});\nconst sourceOptions = computed(() => {\n const options = props.columnOptions.map((v) => ({ value: v.id, label: v.label ?? v }));\n if (inconsistentSourceSelected.value) {\n options.unshift({ value: props.filter.column, label: \"Inconsistent value\" });\n }\n return options;\n});\n\nfunction isPTableColumnIdObject(c: PlAdvancedFilterColumnId): c is PTableColumnId {\n return typeof c === \"object\" && c !== null;\n}\n\nfunction getSourceId(column: PlAdvancedFilterColumnId): PlAdvancedFilterColumnId {\n // PTableColumnId object form: no axisFilters embedded, return as-is.\n if (isPTableColumnIdObject(column)) return column;\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return stringifyColumnId(parsedColumnId.source);\n } else {\n return column;\n }\n } catch {\n return column;\n }\n}\n\n// similar to FilteredPColumnId but source is stringified and axis filters can be undefined\ntype ColumnAsSourceAndFixedAxes = {\n source: PlAdvancedFilterColumnId;\n axisFiltersByIndex: Record<number, AxisFilterValue | undefined>;\n};\nfunction getColumnAsSourceAndFixedAxes(\n column: PlAdvancedFilterColumnId,\n): ColumnAsSourceAndFixedAxes {\n const sourceId = getSourceId(column);\n const option = props.columnOptions.find((op) => columnIdEquals(op.id, sourceId));\n const axesToBeFixed = (option?.axesToBeFixed ?? []).reduce(\n (res, item) => {\n res[item.idx] = undefined;\n return res;\n },\n {} as Record<number, AxisFilterValue | undefined>,\n );\n // PTableColumnId object form has no axis filters baked in.\n if (isPTableColumnIdObject(column)) {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n try {\n const parsedColumnId = parseColumnId(column as SUniversalPColumnId);\n if (isFilteredPColumn(parsedColumnId)) {\n return {\n source: sourceId,\n axisFiltersByIndex: (parsedColumnId.axisFilters as AxisFilterByIdx[]).reduce(\n (res, item) => {\n res[item[0]] = item[1];\n return res;\n },\n axesToBeFixed,\n ),\n };\n }\n } catch {\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n }\n return { source: column, axisFiltersByIndex: axesToBeFixed };\n}\n\n/** Identity comparison for PlAdvancedFilterColumnId values — handles both string\n * and object (PTableColumnId) variants. */\nfunction columnIdEquals(a: PlAdvancedFilterColumnId, b: PlAdvancedFilterColumnId): boolean {\n if (a === b) return true;\n if (typeof a === \"object\" && typeof b === \"object\" && a !== null && b !== null) {\n return canonicalizeJson(a) === canonicalizeJson(b);\n }\n return false;\n}\n\nfunction stringifyColumn(value: ColumnAsSourceAndFixedAxes): PlAdvancedFilterColumnId {\n if (Object.keys(value.axisFiltersByIndex).length === 0) {\n return value.source;\n }\n // PTableColumnId object form can't carry axisFilters — return as-is.\n if (isPTableColumnIdObject(value.source)) return value.source;\n return stringifyColumnId({\n source: parseColumnId(value.source as SUniversalPColumnId) as unknown as AnchoredPColumnId,\n axisFilters: Object.entries(value.axisFiltersByIndex).map(\n ([idx, value]) => [Number(idx), value] as AxisFilterByIdx,\n ),\n });\n}\n\nconst columnAsSourceAndFixedAxes = computed({\n get: () => {\n return getColumnAsSourceAndFixedAxes(props.filter.column);\n },\n set: (value) => {\n props.onUpdateFilter({ ...props.filter, column: stringifyColumn(value) });\n },\n});\nfunction updateAxisFilterValue(idx: number, value: AxisFilterValue | undefined) {\n columnAsSourceAndFixedAxes.value = {\n ...columnAsSourceAndFixedAxes.value,\n axisFiltersByIndex: { ...columnAsSourceAndFixedAxes.value.axisFiltersByIndex, [idx]: value },\n };\n}\n\nconst currentOption = computed(() =>\n props.columnOptions.find((op) => columnIdEquals(op.id, columnAsSourceAndFixedAxes.value.source)),\n);\nconst currentSpec = computed(() =>\n currentOption.value?.spec ? getNormalizedSpec(currentOption.value.spec) : null,\n);\nconst currentType = computed(() => currentSpec.value?.valueType);\nconst currentError = computed(\n () => Boolean(currentOption.value?.error) || inconsistentSourceSelected.value,\n);\n\nconst filterTypesOptions = computed(() =>\n props.supportedFilters\n .filter(\n (v) =>\n props.filter.type === v ||\n (currentSpec.value ? getFilterInfo(v).supportedFor(currentSpec.value) : true),\n )\n .map((v) => ({ value: v, label: getFilterInfo(v).label })),\n);\n\nconst wildcardOptions = computed(() => {\n if (props.filter.type !== \"patternFuzzyContainSubsequence\") {\n return [];\n }\n if (currentOption.value?.alphabet === \"nucleotide\") {\n return [{ label: \"N\", value: \"N\" }];\n }\n if (currentOption.value?.alphabet === \"aminoacid\") {\n return [{ label: \"X\", value: \"X\" }];\n }\n return [...new Set(props.filter.value.split(\"\"))].sort().map((v) => ({ value: v, label: v }));\n});\n\nconst stringMatchesError = computed(() => {\n if (props.filter.type !== \"patternMatchesRegularExpression\") {\n return false;\n }\n try {\n new RegExp(props.filter.value);\n return false;\n } catch {\n return true;\n }\n});\n</script>\n<template>\n <div data-testid=\"pl-advanced-filter-filter\" :class=\"$style.filterWrapper\">\n <!-- top element - column selector / column label - for all filter types-->\n <div\n v-if=\"enableDnd\"\n data-testid=\"pl-advanced-filter-column-chip\"\n :class=\"[$style.top, $style.columnChip, { [$style.error]: currentError }]\"\n >\n <div :class=\"[$style.typeIcon, { [$style.error]: currentError }]\">\n <PlIcon16 v-if=\"currentError\" name=\"warning\" />\n <PlIcon16\n v-else\n :name=\"\n currentType === 'String' || currentType === undefined\n ? 'cell-type-txt'\n : 'cell-type-num'\n \"\n />\n </div>\n <div\n data-testid=\"pl-advanced-filter-column-title\"\n :class=\"$style.titleWrapper\"\n :title=\"currentOption?.label ?? ''\"\n >\n <div :class=\"$style.title\">\n {{\n inconsistentSourceSelected\n ? \"Inconsistent value\"\n : (currentOption?.label ?? props.filter.column)\n }}\n </div>\n </div>\n <div\n data-testid=\"pl-advanced-filter-remove\"\n :class=\"$style.closeIcon\"\n @click=\"onDelete(props.filter.column)\"\n >\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n <div v-else :class=\"$style.top\">\n <PlDropdown\n data-testid=\"pl-advanced-filter-column\"\n :model-value=\"columnAsSourceAndFixedAxes.source\"\n :errorStatus=\"currentError\"\n :options=\"sourceOptions\"\n :style=\"{ width: '100%' }\"\n group-position=\"top-left\"\n @update:model-value=\"changeSourceId\"\n />\n <div\n data-testid=\"pl-advanced-filter-remove\"\n :class=\"$style.closeButton\"\n @click=\"onDelete(props.filter.column)\"\n >\n <PlIcon16 name=\"close\" />\n </div>\n </div>\n\n <div\n v-if=\"currentOption?.axesToBeFixed?.length\"\n data-testid=\"pl-advanced-filter-fixed-axes\"\n :class=\"$style.fixedAxesBlock\"\n >\n <template v-for=\"value in currentOption?.axesToBeFixed\" :key=\"value.idx\">\n <PlAutocomplete\n data-testid=\"pl-advanced-filter-fixed-axis\"\n :model-value=\"columnAsSourceAndFixedAxes.axisFiltersByIndex[value.idx]\"\n :label=\"value.label\"\n :options-search=\"\n (str, type) =>\n getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str, value.idx)\n \"\n :disabled=\"inconsistentSourceSelected\"\n :clearable=\"true\"\n @update:model-value=\"(v) => updateAxisFilterValue(value.idx, v)\"\n />\n </template>\n </div>\n\n <!-- middle - filter type selector - for all filter types -->\n <div\n :class=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA'\n ? $style.bottom\n : $style.middle\n \"\n >\n <PlDropdown\n data-testid=\"pl-advanced-filter-type\"\n :model-value=\"props.filter.type\"\n :options=\"filterTypesOptions\"\n :group-position=\"\n props.filter.type === 'isNA' || props.filter.type === 'isNotNA' ? 'bottom' : 'middle'\n \"\n @update:model-value=\"changeFilterType\"\n />\n </div>\n\n <!-- middle - for fuzzy contains filter -->\n <template v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\">\n <div :class=\"$style.middle\">\n <PlTextField\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"middle\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n </div>\n <div data-testid=\"pl-advanced-filter-fuzzy-settings\" :class=\"$style.innerSection\">\n <Slider\n data-testid=\"pl-advanced-filter-max-edits\"\n :model-value=\"props.filter.maxEdits\"\n :max=\"5\"\n breakpoints\n label=\"Maximum number of substitutions and indels\"\n @update:model-value=\"(v) => updateFilterProp('maxEdits', v)\"\n />\n <PlToggleSwitch\n data-testid=\"pl-advanced-filter-substitutions-only\"\n :model-value=\"props.filter.substitutionsOnly\"\n label=\"Substitutions only\"\n @update:model-value=\"(v) => updateFilterProp('substitutionsOnly', v)\"\n />\n </div>\n </template>\n\n <!-- bottom element - individual settings for every filter type -->\n <div :class=\"$style.bottom\">\n <PlAutocomplete\n v-if=\"props.filter.type === 'patternEquals' || props.filter.type === 'patternNotEquals'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :clearable=\"true\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlAutocompleteMulti\n v-if=\"props.filter.type === 'inSet' || props.filter.type === 'notInSet'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :options-search=\"\n (str, type) => getMultiSuggestOptionsFn(columnAsSourceAndFixedAxes.source, type, str)\n \"\n :disabled=\"inconsistentSourceSelected\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlNumberField\n v-if=\"isNumericFilter(props.filter)\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.x\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('x', v)\"\n />\n <PlNumberField\n v-if=\"isPositionFilter(props.filter)\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.n\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('n', v)\"\n />\n <PlTextField\n v-if=\"\n props.filter.type === 'patternContainSubsequence' ||\n props.filter.type === 'patternNotContainSubsequence'\n \"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n placeholder=\"Substring\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlTextField\n v-if=\"props.filter.type === 'patternMatchesRegularExpression'\"\n data-testid=\"pl-advanced-filter-value\"\n :model-value=\"props.filter.value\"\n :error=\"stringMatchesError ? 'Regular expression is not valid' : undefined\"\n placeholder=\"Regular expression\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('value', v)\"\n />\n <PlDropdown\n v-if=\"props.filter.type === 'patternFuzzyContainSubsequence'\"\n data-testid=\"pl-advanced-filter-wildcard\"\n :model-value=\"props.filter.wildcard\"\n clearable\n placeholder=\"Wildcard value\"\n :options=\"wildcardOptions\"\n group-position=\"bottom\"\n @update:model-value=\"(v) => updateFilterProp('wildcard', v)\"\n />\n </div>\n </div>\n <OperandButton :active=\"operand\" :disabled=\"isLast\" @select=\"onChangeOperand\" />\n</template>\n\n<style module>\n.filterWrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n cursor: default;\n}\n\n.typeIcon {\n display: inline-flex;\n margin-right: 8px;\n}\n\n.typeIcon.error {\n --icon-color: var(--txt-error);\n}\n\n.closeIcon {\n display: inline-flex;\n margin-left: 12px;\n cursor: pointer;\n}\n\n.titleWrapper {\n flex-grow: 1;\n overflow: hidden;\n}\n.title {\n overflow: hidden;\n color: var(--txt-01);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n line-height: 20px;\n}\n\n.columnChip {\n width: 100%;\n display: flex;\n padding: 10px 12px;\n align-items: center;\n border-radius: 6px;\n border: 1px solid var(--txt-01);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n\n &.error {\n border-color: var(--txt-error);\n }\n}\n\n.innerSection {\n border: 1px solid var(--txt-01);\n border-top: none;\n padding: 16px 12px;\n}\n\n.closeButton {\n border: 1px solid var(--txt-01);\n border-top-right-radius: 6px;\n border-left: none;\n width: 40px;\n height: 40px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.top {\n position: relative;\n display: flex;\n width: 100%;\n z-index: 1;\n background: #fff;\n}\n\n.fixedAxesBlock {\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 8px;\n gap: 12px;\n border-left: 1px solid var(--txt-01);\n border-right: 1px solid var(--txt-01);\n}\n\n.fixedAxesBlock > * {\n background: #fff;\n}\n\n.middle,\n.bottom {\n position: relative;\n margin-top: -1px;\n background: #fff;\n}\n</style>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"OperandButton.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { Operand } from \"./types\";\n\nconst props = defineProps<{\n active: Operand;\n disabled: boolean;\n onSelect: (op: Operand) => void;\n}>();\n\nconst OPTIONS: Operand[] = [\"and\", \"or\"];\n</script>\n<template>\n <div v-bind=\"$attrs\" :class=\"$style.block\">\n <div\n v-for=\"op in OPTIONS\"\n :key=\"op\"\n :class=\"[$style.operand, { [$style.active]: op === props.active && !props.disabled }]\"\n @click=\"!props.disabled && props.onSelect(op)\"\n >\n {{ op }}\n </div>\n </div>\n</template>\n<style module>\n.block {\n width: 100%;\n display: flex;\n gap: 4px;\n justify-content: center;\n height: 30px;\n align-items: center;\n}\n.operand {\n border-radius: 16px;\n width: 64px;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-transform: uppercase;\n font-weight: 600;\n\n color: var(--txt-03);\n border: 1px solid var(--color-div-grey);\n background: transparent;\n cursor: pointer;\n}\n.operand.active {\n color: var(--txt-01);\n background: #fff;\n border: 1px solid var(--txt-01);\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"OperandButton.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { Operand } from \"./types\";\n\nconst props = defineProps<{\n active: Operand;\n disabled: boolean;\n onSelect: (op: Operand) => void;\n}>();\n\nconst OPTIONS: Operand[] = [\"and\", \"or\"];\n</script>\n<template>\n <div data-testid=\"pl-advanced-filter-operands\" v-bind=\"$attrs\" :class=\"$style.block\">\n <div\n v-for=\"op in OPTIONS\"\n :key=\"op\"\n data-testid=\"pl-advanced-filter-operand\"\n :class=\"[$style.operand, { [$style.active]: op === props.active && !props.disabled }]\"\n @click=\"!props.disabled && props.onSelect(op)\"\n >\n {{ op }}\n </div>\n </div>\n</template>\n<style module>\n.block {\n width: 100%;\n display: flex;\n gap: 4px;\n justify-content: center;\n height: 30px;\n align-items: center;\n}\n.operand {\n border-radius: 16px;\n width: 64px;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-transform: uppercase;\n font-weight: 600;\n\n color: var(--txt-03);\n border: 1px solid var(--color-div-grey);\n background: transparent;\n cursor: pointer;\n}\n.operand.active {\n color: var(--txt-01);\n background: #fff;\n border: 1px solid var(--txt-01);\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"OperandButton.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { Operand } from \"./types\";\n\nconst props = defineProps<{\n active: Operand;\n disabled: boolean;\n onSelect: (op: Operand) => void;\n}>();\n\nconst OPTIONS: Operand[] = [\"and\", \"or\"];\n</script>\n<template>\n <div v-bind=\"$attrs\" :class=\"$style.block\">\n <div\n v-for=\"op in OPTIONS\"\n :key=\"op\"\n :class=\"[$style.operand, { [$style.active]: op === props.active && !props.disabled }]\"\n @click=\"!props.disabled && props.onSelect(op)\"\n >\n {{ op }}\n </div>\n </div>\n</template>\n<style module>\n.block {\n width: 100%;\n display: flex;\n gap: 4px;\n justify-content: center;\n height: 30px;\n align-items: center;\n}\n.operand {\n border-radius: 16px;\n width: 64px;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-transform: uppercase;\n font-weight: 600;\n\n color: var(--txt-03);\n border: 1px solid var(--color-div-grey);\n background: transparent;\n cursor: pointer;\n}\n.operand.active {\n color: var(--txt-01);\n background: #fff;\n border: 1px solid var(--txt-01);\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"OperandButton.vue_vue_type_style_index_0_lang.module.js","names":[],"sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { Operand } from \"./types\";\n\nconst props = defineProps<{\n active: Operand;\n disabled: boolean;\n onSelect: (op: Operand) => void;\n}>();\n\nconst OPTIONS: Operand[] = [\"and\", \"or\"];\n</script>\n<template>\n <div data-testid=\"pl-advanced-filter-operands\" v-bind=\"$attrs\" :class=\"$style.block\">\n <div\n v-for=\"op in OPTIONS\"\n :key=\"op\"\n data-testid=\"pl-advanced-filter-operand\"\n :class=\"[$style.operand, { [$style.active]: op === props.active && !props.disabled }]\"\n @click=\"!props.disabled && props.onSelect(op)\"\n >\n {{ op }}\n </div>\n </div>\n</template>\n<style module>\n.block {\n width: 100%;\n display: flex;\n gap: 4px;\n justify-content: center;\n height: 30px;\n align-items: center;\n}\n.operand {\n border-radius: 16px;\n width: 64px;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-transform: uppercase;\n font-weight: 600;\n\n color: var(--txt-03);\n border: 1px solid var(--color-div-grey);\n background: transparent;\n cursor: pointer;\n}\n.operand.active {\n color: var(--txt-01);\n background: #fff;\n border: 1px solid var(--txt-01);\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"OperandButton.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"names":[],"mappings":"AAwDA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC;CACjC,CAAC;AAoDF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"OperandButton.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"names":[],"mappings":"AAyDA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC;CACjC,CAAC;AAsDF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -9,8 +9,9 @@ var l = ["onClick"], u = /* @__PURE__ */ r({
9
9
  },
10
10
  setup(r) {
11
11
  let u = r, d = ["and", "or"];
12
- return (r, f) => (o(), t("div", i(r.$attrs, { class: r.$style.block }), [(o(), t(e, null, s(d, (e) => n("div", {
12
+ return (r, f) => (o(), t("div", i({ "data-testid": "pl-advanced-filter-operands" }, r.$attrs, { class: r.$style.block }), [(o(), t(e, null, s(d, (e) => n("div", {
13
13
  key: e,
14
+ "data-testid": "pl-advanced-filter-operand",
14
15
  class: a([r.$style.operand, { [r.$style.active]: e === u.active && !u.disabled }]),
15
16
  onClick: (t) => !u.disabled && u.onSelect(e)
16
17
  }, c(e), 11, l)), 64))], 16));
@@ -1 +1 @@
1
- {"version":3,"file":"OperandButton.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$style"],"sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { Operand } from \"./types\";\n\nconst props = defineProps<{\n active: Operand;\n disabled: boolean;\n onSelect: (op: Operand) => void;\n}>();\n\nconst OPTIONS: Operand[] = [\"and\", \"or\"];\n</script>\n<template>\n <div v-bind=\"$attrs\" :class=\"$style.block\">\n <div\n v-for=\"op in OPTIONS\"\n :key=\"op\"\n :class=\"[$style.operand, { [$style.active]: op === props.active && !props.disabled }]\"\n @click=\"!props.disabled && props.onSelect(op)\"\n >\n {{ op }}\n </div>\n </div>\n</template>\n<style module>\n.block {\n width: 100%;\n display: flex;\n gap: 4px;\n justify-content: center;\n height: 30px;\n align-items: center;\n}\n.operand {\n border-radius: 16px;\n width: 64px;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-transform: uppercase;\n font-weight: 600;\n\n color: var(--txt-03);\n border: 1px solid var(--color-div-grey);\n background: transparent;\n cursor: pointer;\n}\n.operand.active {\n color: var(--txt-01);\n background: #fff;\n border: 1px solid var(--txt-01);\n}\n</style>\n"],"mappings":";;;;;;;;;;EAGA,IAAM,IAAQ,GAMR,IAAqB,CAAC,OAAO,KAAK;yBAGtC,EASM,OATN,EAAaA,EASP,QATa,EAAG,OAAOC,EAAAA,OAAO,OAAA,CAAA,EAAA,EAAA,GAAA,EAClC,EAOM,GAAA,MAAA,EANS,IAAN,MADT,EAOM,OAAA;GALH,KAAK;GACL,OAAK,EAAA,CAAGA,EAAAA,OAAO,SAAO,GAAKA,EAAAA,OAAO,SAAS,MAAO,EAAM,UAAM,CAAK,EAAM,UAAQ,CAAA,CAAA;GACjF,UAAK,MAAA,CAAG,EAAM,YAAY,EAAM,SAAS,EAAE;OAEzC,EAAE,EAAA,IAAA,EAAA"}
1
+ {"version":3,"file":"OperandButton.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$style"],"sources":["../../../src/components/PlAdvancedFilter/OperandButton.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { Operand } from \"./types\";\n\nconst props = defineProps<{\n active: Operand;\n disabled: boolean;\n onSelect: (op: Operand) => void;\n}>();\n\nconst OPTIONS: Operand[] = [\"and\", \"or\"];\n</script>\n<template>\n <div data-testid=\"pl-advanced-filter-operands\" v-bind=\"$attrs\" :class=\"$style.block\">\n <div\n v-for=\"op in OPTIONS\"\n :key=\"op\"\n data-testid=\"pl-advanced-filter-operand\"\n :class=\"[$style.operand, { [$style.active]: op === props.active && !props.disabled }]\"\n @click=\"!props.disabled && props.onSelect(op)\"\n >\n {{ op }}\n </div>\n </div>\n</template>\n<style module>\n.block {\n width: 100%;\n display: flex;\n gap: 4px;\n justify-content: center;\n height: 30px;\n align-items: center;\n}\n.operand {\n border-radius: 16px;\n width: 64px;\n height: 32px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-transform: uppercase;\n font-weight: 600;\n\n color: var(--txt-03);\n border: 1px solid var(--color-div-grey);\n background: transparent;\n cursor: pointer;\n}\n.operand.active {\n color: var(--txt-01);\n background: #fff;\n border: 1px solid var(--txt-01);\n}\n</style>\n"],"mappings":";;;;;;;;;;EAGA,IAAM,IAAQ,GAMR,IAAqB,CAAC,OAAO,KAAK;yBAGtC,EAUM,OAVN,EAUM,EAVD,eAAY,+BAA6B,EAASA,EAAAA,QAAM,EAAG,OAAOC,EAAAA,OAAO,OAAA,CAAA,EAAA,EAAA,GAAA,EAC5E,EAQM,GAAA,MAAA,EAPS,IAAN,MADT,EAQM,OAAA;GANH,KAAK;GACN,eAAY;GACX,OAAK,EAAA,CAAGA,EAAAA,OAAO,SAAO,GAAKA,EAAAA,OAAO,SAAS,MAAO,EAAM,UAAM,CAAK,EAAM,UAAQ,CAAA,CAAA;GACjF,UAAK,MAAA,CAAG,EAAM,YAAY,EAAM,SAAS,EAAE;OAEzC,EAAE,EAAA,IAAA,EAAA"}