@platforma-sdk/ui-vue 1.66.2 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +14 -10
  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 +15 -0
  6. package/dist/__tests__/setup.d.ts +2 -0
  7. package/dist/__tests__/setup.d.ts.map +1 -0
  8. package/dist/components/PlAnnotations/components/PlAnnotations.vue2.js.map +1 -1
  9. package/dist/components/PlDatasetSelector/PlDatasetSelector.js +9 -0
  10. package/dist/components/PlDatasetSelector/PlDatasetSelector.js.map +1 -0
  11. package/dist/components/PlDatasetSelector/PlDatasetSelector.style.css +1 -0
  12. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue.d.ts +105 -0
  13. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue.d.ts.map +1 -0
  14. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue2.js +111 -0
  15. package/dist/components/PlDatasetSelector/PlDatasetSelector.vue2.js.map +1 -0
  16. package/dist/components/PlDatasetSelector/__tests__/PlDatasetSelector.jsdomtest.d.ts +2 -0
  17. package/dist/components/PlDatasetSelector/__tests__/PlDatasetSelector.jsdomtest.d.ts.map +1 -0
  18. package/dist/components/PlDatasetSelector/index.d.ts +2 -0
  19. package/dist/components/PlDatasetSelector/index.d.ts.map +1 -0
  20. package/dist/components/PlDatasetSelector/index.js +1 -0
  21. package/dist/index.js +15 -14
  22. package/dist/index.js.map +1 -1
  23. package/dist/lib.d.ts +1 -0
  24. package/dist/lib.d.ts.map +1 -1
  25. package/dist/lib.js +2 -0
  26. package/package.json +9 -7
  27. package/src/__tests__/setup.ts +10 -0
  28. package/src/components/PlDatasetSelector/PlDatasetSelector.vue +164 -0
  29. package/src/components/PlDatasetSelector/__tests__/PlDatasetSelector.jsdomtest.ts +257 -0
  30. package/src/components/PlDatasetSelector/index.ts +1 -0
  31. package/src/lib.ts +2 -0
  32. package/vitest.config.mts +22 -0
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.66.2 build /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.67.0 build /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder build --target browser-lib
5
5
 
6
6
  Building browser-lib project...
@@ -12,11 +12,11 @@ Building browser-lib project...
12
12
  4 export function useGridColumns(props: { api: GridApi }) {
13
13
     ~~~~~~~~~~~~~~
14
14
 
15
- 
15
+ 
16
16
  rendering chunks...
17
17
 
18
18
  [vite:dts] Start generate declaration files...
19
- [vite:dts] Declaration files built in 4461ms.
19
+ [vite:dts] Declaration files built in 5238ms.
20
20
 
21
21
  computing gzip size...
22
22
  dist/components/PlAnnotations/components/PlAnnotations.vue?vue&type=style&index=0&lang.css 0.04 kB │ gzip: 0.06 kB
@@ -25,6 +25,7 @@ dist/components/BlockLayout.vue?vue&type=style&index=0&lang.css
25
25
  dist/components/PlAgDataTable/PlAgDataTableSheets.vue?vue&type=style&index=0&lang.css 0.06 kB │ gzip: 0.08 kB
26
26
  dist/components/PlAnnotations/components/PlAnnotationsModal.vue?vue&type=style&index=0&lang.css 0.07 kB │ gzip: 0.08 kB
27
27
  dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue?vue&type=style&index=0&lang.css 0.07 kB │ gzip: 0.09 kB
28
+ dist/components/PlDatasetSelector/PlDatasetSelector.vue_vue_type_style_index_0_scoped_b52c3499_lang.css 0.08 kB │ gzip: 0.09 kB
28
29
  dist/components/PlAgDataTable/PlAgDataTableV2.vue?vue&type=style&index=0&lang.css 0.09 kB │ gzip: 0.10 kB
29
30
  dist/components/PlAnnotations/components/FilterSidebar.vue?vue&type=style&index=0&lang.css 0.11 kB │ gzip: 0.09 kB
30
31
  dist/components/PlAgChartHistogramCell/PlAgChartHistogramCell.vue_vue_type_style_index_0_lang.css 0.16 kB │ gzip: 0.13 kB
@@ -57,6 +58,7 @@ dist/components/PlAgCellProgress/index.js
57
58
  dist/components/PlAgColumnHeader/index.js 0.03 kB │ gzip: 0.05 kB
58
59
  dist/components/PlTableFilters/index.js 0.03 kB │ gzip: 0.05 kB
59
60
  dist/components/PlAgCellStatusTag/index.js 0.03 kB │ gzip: 0.05 kB
61
+ dist/components/PlDatasetSelector/index.js 0.03 kB │ gzip: 0.05 kB
60
62
  dist/components/PlTableFastSearch/index.js 0.03 kB │ gzip: 0.05 kB
61
63
  dist/components/PlAgRowNumCheckbox/index.js 0.03 kB │ gzip: 0.05 kB
62
64
  dist/components/PlBtnExportArchive/index.js 0.03 kB │ gzip: 0.05 kB
@@ -144,6 +146,7 @@ dist/objectHash.js
144
146
  dist/composition/AgGrid/index.js 0.46 kB │ gzip: 0.32 kB │ map: 0.79 kB
145
147
  dist/defineStore.js 0.49 kB │ gzip: 0.32 kB │ map: 1.20 kB
146
148
  dist/internal/utils.js 0.50 kB │ gzip: 0.29 kB │ map: 1.08 kB
149
+ dist/components/PlDatasetSelector/PlDatasetSelector.js 0.51 kB │ gzip: 0.29 kB │ map: 1.10 kB
147
150
  dist/components/LoaderPage.js 0.52 kB │ gzip: 0.35 kB │ map: 0.48 kB
148
151
  dist/components/PlAdvancedFilter/PlAdvancedFilter.vue_vue_type_style_index_0_lang.module.js 0.54 kB │ gzip: 0.29 kB │ map: 0.72 kB
149
152
  dist/plugins/Monetization/useButtonTarget.js 0.54 kB │ gzip: 0.35 kB │ map: 1.10 kB
@@ -198,12 +201,13 @@ dist/plugins/Monetization/UserCabinetCard.vue_vue_type_script_setup_true_lang.js
198
201
  dist/components/BlockLayout.vue_vue_type_script_setup_true_lang.js 2.18 kB │ gzip: 1.03 kB │ map: 5.92 kB
199
202
  dist/components/PlAppErrorNotificationAlert/PlAppErrorNotificationAlert.vue_vue_type_script_setup_true_lang.js 2.40 kB │ gzip: 1.12 kB │ map: 6.67 kB
200
203
  dist/components/PlAgDataTable/PlAgDataTableSheets.vue_vue_type_script_setup_true_lang.js 2.45 kB │ gzip: 1.17 kB │ map: 5.01 kB
201
- dist/lib.js 2.60 kB │ gzip: 0.56 kB │ map: 3.82 kB
202
204
  dist/components/PlAnnotations/components/PlAnnotations.vue_vue_type_script_setup_true_lang.js 2.69 kB │ gzip: 1.12 kB │ map: 4.16 kB
205
+ dist/lib.js 2.71 kB │ gzip: 0.57 kB │ map: 3.97 kB
203
206
  dist/components/PlAgDataTable/compositions/useGrid.js 2.80 kB │ gzip: 1.24 kB │ map: 6.28 kB
204
207
  dist/components/PlAgColumnHeader/PlAgColumnHeader.vue_vue_type_script_setup_true_lang.js 2.93 kB │ gzip: 1.26 kB │ map: 7.97 kB
205
208
  dist/components/PlAgDataTable/sources/row-number.js 2.99 kB │ gzip: 1.31 kB │ map: 7.42 kB
206
209
  dist/defineApp.js 3.01 kB │ gzip: 1.05 kB │ map: 14.38 kB
210
+ dist/components/PlDatasetSelector/PlDatasetSelector.vue_vue_type_script_setup_true_lang.js 3.09 kB │ gzip: 1.21 kB │ map: 6.90 kB
207
211
  dist/components/PlAgGridColumnManager/PlAgGridColumnManager.vue_vue_type_script_setup_true_lang.js 3.14 kB │ gzip: 1.42 kB │ map: 5.04 kB
208
212
  dist/plugins/Monetization/LimitCard.vue_vue_type_script_setup_true_lang.js 3.31 kB │ gzip: 1.15 kB │ map: 9.08 kB
209
213
  dist/composition/fileContent.js 3.78 kB │ gzip: 1.45 kB │ map: 12.95 kB
@@ -212,7 +216,7 @@ dist/plugins/Monetization/MonetizationSidebar.vue_vue_type_script_setup_true_lan
212
216
  dist/components/PlAnnotations/components/FilterSidebar.vue_vue_type_script_setup_true_lang.js 3.97 kB │ gzip: 1.56 kB │ map: 7.42 kB
213
217
  dist/internal/createAppV1.js 4.01 kB │ gzip: 1.42 kB │ map: 14.21 kB
214
218
  dist/AgGridVue/useAgGridOptions.js 4.23 kB │ gzip: 1.33 kB │ map: 14.12 kB
215
- dist/index.js 4.42 kB │ gzip: 1.35 kB │ map: 0.25 kB
219
+ dist/index.js 4.52 kB │ gzip: 1.37 kB │ map: 0.25 kB
216
220
  dist/components/PlTableFilters/PlTableFiltersV2.vue_vue_type_script_setup_true_lang.js 4.63 kB │ gzip: 1.91 kB │ map: 9.02 kB
217
221
  dist/components/PlBtnExportArchive/PlBtnExportArchive.vue_vue_type_script_setup_true_lang.js 5.19 kB │ gzip: 2.16 kB │ map: 10.36 kB
218
222
  dist/internal/createAppV2.js 5.26 kB │ gzip: 1.98 kB │ map: 17.78 kB
@@ -224,12 +228,12 @@ dist/components/PlAdvancedFilter/FilterEditor.vue_vue_type_script_setup_true_lan
224
228
  dist/components/PlAgDataTable/PlAgDataTableV2.vue_vue_type_script_setup_true_lang.js 12.10 kB │ gzip: 3.86 kB │ map: 28.98 kB
225
229
 
226
230
  [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:
227
- - sourcemaps (30%)
228
- - vite:dts (23%)
229
- - vite:css-post (15%)
231
+ - sourcemaps (35%)
232
+ - vite:dts (25%)
233
+ - vite:css-post (11%)
230
234
  - vite:vue (9%)
231
- - vite:build-import-analysis (6%)
235
+ - vite:css (7%)
232
236
  See https://rolldown.rs/options/checks#plugintimings for more details.
233
237
  
234
- ✓ built in 5.29s
238
+ ✓ built in 6.07s
235
239
  Build completed successfully
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.66.2 formatter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.67.0 formatter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder formatter --check
5
5
 
6
6
  Checking formatting...
@@ -8,5 +8,5 @@ Checking formatting...
8
8
  Checking formatting...
9
9
 
10
10
  All matched files use the correct format.
11
- Finished in 3408ms on 132 files using 8 threads.
11
+ Finished in 3092ms on 136 files using 8 threads.
12
12
  Format check completed successfully
@@ -1,10 +1,10 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.66.2 linter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.67.0 linter:check /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder linter --check
5
5
 
6
6
  Linting project...
7
7
  ↳ oxlint --config /home/runner/_work/platforma/platforma/sdk/ui-vue/.oxlintrc.json --deny-warnings
8
8
  Found 0 warnings and 0 errors.
9
- Finished in 33ms on 115 files with 98 rules using 8 threads.
9
+ Finished in 27ms on 119 files with 98 rules using 8 threads.
10
10
  Linting completed successfully
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-sdk/ui-vue@1.66.2 types:check /home/runner/_work/platforma/platforma/sdk/ui-vue
3
+ > @platforma-sdk/ui-vue@1.67.0 types:check /home/runner/_work/platforma/platforma/sdk/ui-vue
4
4
  > ts-builder type-check --target browser-lib
5
5
 
6
6
  ↳ vue-tsc.js --noEmit --project ./tsconfig.json --customConditions ,
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @platforma-sdk/ui-vue
2
2
 
3
+ ## 1.67.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 10eec21: Add `PlDatasetSelector` component for dataset + optional filter selection. Emits `PrimaryRef`; filter dropdown appears only when the selected dataset has compatible filters (progressive disclosure). Accepts both `PrimaryRef` and plain `PlRef` as input for backward compatibility.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [10eec21]
12
+ - Updated dependencies [10eec21]
13
+ - @platforma-sdk/model@1.67.0
14
+ - @milaboratories/pl-model-common@1.35.0
15
+ - @milaboratories/uikit@2.12.9
16
+ - @milaboratories/pf-spec-driver@1.3.3
17
+
3
18
  ## 1.66.2
4
19
 
5
20
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"PlAnnotations.vue_vue_type_script_setup_true_lang.js","names":["$style"],"sources":["../../../../src/components/PlAnnotations/components/PlAnnotations.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { Props as BaseProps } from \"./FilterSidebar.vue\";\nexport type Props = Omit<BaseProps, \"step\" | \"onUpdateStep\"> & {\n annotation: Annotation;\n onUpdateAnnotation: (annotation: Annotation) => void;\n onDeleteSchema?: () => void;\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, effect, shallowRef } from \"vue\";\n\nimport { isNil } from \"@milaboratories/helpers\";\nimport { produce } from \"immer\";\nimport { PlSidebarGroup, useConfirm } from \"@milaboratories/uikit\";\n\nimport type { Annotation, Filter } from \"../types\";\nimport AnnotationsSidebar from \"./AnnotationsSidebar.vue\";\nimport FilterSidebar from \"./FilterSidebar.vue\";\n\nconst props = defineProps<Props>();\n\nconst selectedStepId = shallowRef<undefined | number>(undefined);\n\nconst selectedStep = computed(() => {\n return isNil(selectedStepId.value) || isNil(props.annotation)\n ? undefined\n : props.annotation.steps.find((step) => step.id === selectedStepId.value);\n});\n\neffect(function setDefaultStepId() {\n if (selectedStepId.value === undefined && props.annotation.steps.length > 0) {\n selectedStepId.value = props.annotation.steps[0].id;\n }\n});\n\nconst confirmResetSchema = useConfirm({\n title: \"Reset Schema\",\n message: \"Are you sure you want to reset the schema? This action cannot be undone.\",\n confirmLabel: \"Yes, reset\",\n cancelLabel: \"No, cancel\",\n});\n\nasync function handleDeleteSchema() {\n if (await confirmResetSchema()) {\n selectedStepId.value = undefined;\n props.onDeleteSchema?.();\n }\n}\n\nfunction updateSelectedStepId(id: undefined | number) {\n selectedStepId.value = id;\n}\n\nfunction updateSelectedStep(step: Filter) {\n props.onUpdateAnnotation(\n produce(props.annotation, (draft) => {\n const idx = draft.steps.findIndex((s) => s.id === step.id);\n if (idx !== -1) {\n draft.steps[idx] = step;\n }\n }),\n );\n}\n</script>\n\n<template>\n <PlSidebarGroup>\n <template #item-0>\n <AnnotationsSidebar\n :annotation=\"props.annotation\"\n :selected-step-id=\"selectedStepId\"\n :on-update-annotation=\"props.onUpdateAnnotation\"\n :on-update-selected-step-id=\"updateSelectedStepId\"\n :class=\"$style.sidebarItem\"\n @delete-schema=\"handleDeleteSchema\"\n />\n </template>\n <template #item-1>\n <FilterSidebar\n v-if=\"selectedStep\"\n :step=\"selectedStep\"\n :on-update-step=\"updateSelectedStep\"\n :class=\"$style.sidebarItem\"\n :columns=\"props.columns\"\n :get-suggest-options=\"props.getSuggestOptions\"\n :hasSelectedColumns=\"props.hasSelectedColumns\"\n :getValuesForSelectedColumns=\"props.getValuesForSelectedColumns\"\n />\n </template>\n </PlSidebarGroup>\n</template>\n\n<style module>\n.sidebarItem {\n width: 100%;\n height: 100%;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAoBA,IAAM,IAAQ,GAER,IAAiB,EAA+B,KAAA,EAAU,EAE1D,IAAe,QACZ,EAAM,EAAe,MAAM,IAAI,EAAM,EAAM,WAAU,GACxD,KAAA,IACA,EAAM,WAAW,MAAM,MAAM,MAAS,EAAK,OAAO,EAAe,MAAM,CAC3E;AAEF,IAAO,WAA4B;AACjC,GAAI,EAAe,UAAU,KAAA,KAAa,EAAM,WAAW,MAAM,SAAS,MACxE,EAAe,QAAQ,EAAM,WAAW,MAAM,GAAG;IAEnD;EAEF,IAAM,IAAqB,EAAW;GACpC,OAAO;GACP,SAAS;GACT,cAAc;GACd,aAAa;GACd,CAAC;EAEF,eAAe,IAAqB;AAClC,GAAI,MAAM,GAAoB,KAC5B,EAAe,QAAQ,KAAA,GACvB,EAAM,kBAAkB;;EAI5B,SAAS,EAAqB,GAAwB;AACpD,KAAe,QAAQ;;EAGzB,SAAS,EAAmB,GAAc;AACxC,KAAM,mBACJ,EAAQ,EAAM,aAAa,MAAU;IACnC,IAAM,IAAM,EAAM,MAAM,WAAW,MAAM,EAAE,OAAO,EAAK,GAAG;AAC1D,IAAI,MAAQ,OACV,EAAM,MAAM,KAAO;KAErB,CACH;;yBAKD,EAuBiB,EAAA,EAAA,EAAA,MAAA;GAtBJ,UAAM,QAQb,CAPF,EAOE,GAAA;IANC,YAAY,EAAM;IAClB,oBAAkB,EAAA;IAClB,wBAAsB,EAAM;IAC5B,8BAA4B;IAC5B,OAAK,EAAEA,EAAAA,OAAO,YAAW;IACzB,gBAAe;;;;;;;GAGT,UAAM,QAUb,CARM,EAAA,SAAA,GAAA,EADR,EASE,GAAA;;IAPC,MAAM,EAAA;IACN,kBAAgB;IAChB,OAAK,EAAEA,EAAAA,OAAO,YAAW;IACzB,SAAS,EAAM;IACf,uBAAqB,EAAM;IAC3B,oBAAoB,EAAM;IAC1B,6BAA6B,EAAM"}
1
+ {"version":3,"file":"PlAnnotations.vue_vue_type_script_setup_true_lang.js","names":["$style"],"sources":["../../../../src/components/PlAnnotations/components/PlAnnotations.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { Props as BaseProps } from \"./FilterSidebar.vue\";\nexport type Props = Omit<BaseProps, \"step\" | \"onUpdateStep\"> & {\n annotation: Annotation;\n onUpdateAnnotation: (annotation: Annotation) => void;\n onDeleteSchema?: () => void;\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, effect, shallowRef } from \"vue\";\n\nimport { isNil } from \"@milaboratories/helpers\";\nimport { produce } from \"immer\";\nimport { PlSidebarGroup, useConfirm } from \"@milaboratories/uikit\";\n\nimport type { Annotation, Filter } from \"../types\";\nimport AnnotationsSidebar from \"./AnnotationsSidebar.vue\";\nimport FilterSidebar from \"./FilterSidebar.vue\";\n\nconst props = defineProps<Props>();\n\nconst selectedStepId = shallowRef<undefined | number>(undefined);\n\nconst selectedStep = computed(() => {\n return isNil(selectedStepId.value) || isNil(props.annotation)\n ? undefined\n : props.annotation.steps.find((step) => step.id === selectedStepId.value);\n});\n\neffect(function setDefaultStepId() {\n if (selectedStepId.value === undefined && props.annotation.steps.length > 0) {\n selectedStepId.value = props.annotation.steps[0].id;\n }\n});\n\nconst confirmResetSchema = useConfirm({\n title: \"Reset Schema\",\n message: \"Are you sure you want to reset the schema? This action cannot be undone.\",\n confirmLabel: \"Yes, reset\",\n cancelLabel: \"No, cancel\",\n});\n\nasync function handleDeleteSchema() {\n if (await confirmResetSchema()) {\n selectedStepId.value = undefined;\n props.onDeleteSchema?.();\n }\n}\n\nfunction updateSelectedStepId(id: undefined | number) {\n selectedStepId.value = id;\n}\n\nfunction updateSelectedStep(step: Filter) {\n props.onUpdateAnnotation(\n produce(props.annotation, (draft) => {\n const idx = draft.steps.findIndex((s) => s.id === step.id);\n if (idx !== -1) {\n draft.steps[idx] = step;\n }\n }),\n );\n}\n</script>\n\n<template>\n <PlSidebarGroup>\n <template #item-0>\n <AnnotationsSidebar\n :annotation=\"props.annotation\"\n :selected-step-id=\"selectedStepId\"\n :on-update-annotation=\"props.onUpdateAnnotation\"\n :on-update-selected-step-id=\"updateSelectedStepId\"\n :class=\"$style.sidebarItem\"\n @delete-schema=\"handleDeleteSchema\"\n />\n </template>\n <template #item-1>\n <FilterSidebar\n v-if=\"selectedStep\"\n :step=\"selectedStep\"\n :on-update-step=\"updateSelectedStep\"\n :class=\"$style.sidebarItem\"\n :columns=\"props.columns\"\n :get-suggest-options=\"props.getSuggestOptions\"\n :hasSelectedColumns=\"props.hasSelectedColumns\"\n :getValuesForSelectedColumns=\"props.getValuesForSelectedColumns\"\n />\n </template>\n </PlSidebarGroup>\n</template>\n\n<style module>\n.sidebarItem {\n width: 100%;\n height: 100%;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAoBA,IAAM,IAAQ,GAER,IAAiB,EAA+B,KAAA,EAAU,EAE1D,IAAe,QACZ,EAAM,EAAe,MAAM,IAAI,EAAM,EAAM,WAAU,GACxD,KAAA,IACA,EAAM,WAAW,MAAM,MAAM,MAAS,EAAK,OAAO,EAAe,MAAM,CAC3E;AAEF,IAAO,WAA4B;AACjC,GAAI,EAAe,UAAU,KAAA,KAAa,EAAM,WAAW,MAAM,SAAS,MACxE,EAAe,QAAQ,EAAM,WAAW,MAAM,GAAG;IAEnD;EAEF,IAAM,IAAqB,EAAW;GACpC,OAAO;GACP,SAAS;GACT,cAAc;GACd,aAAa;GACd,CAAC;EAEF,eAAe,IAAqB;AAClC,GAAI,MAAM,GAAoB,KAC5B,EAAe,QAAQ,KAAA,GACvB,EAAM,kBAAkB;;EAI5B,SAAS,EAAqB,GAAwB;AACpD,KAAe,QAAQ;;EAGzB,SAAS,EAAmB,GAAc;AACxC,KAAM,mBACJ,EAAQ,EAAM,aAAa,MAAU;IACnC,IAAM,IAAM,EAAM,MAAM,WAAW,MAAM,EAAE,OAAO,EAAK,GAAG;AAC1D,IAAI,MAAQ,OACV,EAAM,MAAM,KAAO;KAErB,CACH;;yBAKD,EAuBiB,EAAA,EAAA,EAAA,MAAA;GAtBJ,UAAM,QAQb,CAPF,EAOE,GAAA;IANC,YAAY,EAAM;IAClB,oBAAkB,EAAA;IAClB,wBAAsB,EAAM;IAC5B,8BAA4B;IAC5B,OAAK,EAAEA,EAAAA,OAAO,YAAW;IACzB,gBAAe;;;;;;;GAGT,UAAM,QAiBZ,CAfK,EAAA,SAAA,GAAA,EADR,EASE,GAAA;;IAPC,MAAM,EAAA;IACN,kBAAgB;IAChB,OAAK,EAAEA,EAAAA,OAAO,YAAW;IACzB,SAAS,EAAM;IACf,uBAAqB,EAAM;IAC3B,oBAAoB,EAAM;IAC1B,6BAA6B,EAAM"}
@@ -0,0 +1,9 @@
1
+ import e from "../../_virtual/_plugin-vue_export-helper.js";
2
+ import t from "./PlDatasetSelector.vue2.js";
3
+ import './PlDatasetSelector.style.css';/* empty css */
4
+ //#region src/components/PlDatasetSelector/PlDatasetSelector.vue
5
+ var n = /* @__PURE__ */ e(t, [["__scopeId", "data-v-b52c3499"]]);
6
+ //#endregion
7
+ export { n as default };
8
+
9
+ //# sourceMappingURL=PlDatasetSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlDatasetSelector.js","names":[],"sources":["../../../src/components/PlDatasetSelector/PlDatasetSelector.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Select a dataset and (optionally) a filter column, emitting a `PrimaryRef`.\n *\n * Behaves like {@link PlDropdownRef} when none of the offered datasets carry\n * filter options. When the selected dataset has compatible filters, a second\n * dropdown appears with the filters plus a \"No filter\" entry.\n *\n * Accepts `PrimaryRef | PlRef | undefined` as `modelValue` (a plain `PlRef`\n * is treated as an unfiltered dataset), but always emits `PrimaryRef`\n * (or `undefined` when cleared).\n */\nexport default {\n name: \"PlDatasetSelector\",\n};\n</script>\n\n<script lang=\"ts\" setup>\nimport type { DatasetOption, PlRef, PrimaryRef } from \"@platforma-sdk/model\";\nimport { createPrimaryRef, isPrimaryRef, plRefsEqual } from \"@platforma-sdk/model\";\nimport type { ListOption } from \"@milaboratories/uikit\";\nimport { PlDropdown, PlDropdownRef } from \"@milaboratories/uikit\";\nimport { computed } from \"vue\";\n\nconst slots = defineSlots<{\n tooltip?: () => unknown;\n}>();\n\n/**\n * v-model value. Accepts `PrimaryRef`, plain `PlRef` (treated as unfiltered),\n * or `undefined`. Writes always emit `PrimaryRef` or `undefined`.\n */\nconst model = defineModel<PrimaryRef | PlRef | undefined>();\n\nconst props = withDefaults(\n defineProps<{\n /** Available datasets, each optionally carrying compatible filter choices. */\n options?: Readonly<DatasetOption[]>;\n /** Label above the dataset dropdown. */\n label?: string;\n /** Helper text below the dataset dropdown (shown when there is no error). */\n helper?: string;\n /** Helper text shown while `options` is undefined (loading). */\n loadingOptionsHelper?: string;\n /** Error message displayed below the dataset dropdown. */\n error?: unknown;\n /** Placeholder when no dataset is selected. */\n placeholder?: string;\n /** Label above the filter dropdown. */\n filterLabel?: string;\n /** Placeholder for the filter dropdown. */\n filterPlaceholder?: string;\n /** Label of the \"no filter\" entry prepended to the filter options. */\n noFilterLabel?: string;\n /** Show a clear button on the dataset dropdown. */\n clearable?: boolean;\n /** Mark the dataset dropdown as required. */\n required?: boolean;\n /** Disable all interaction. */\n disabled?: boolean;\n }>(),\n {\n options: undefined,\n label: undefined,\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n filterLabel: \"\",\n filterPlaceholder: \"...\",\n noFilterLabel: \"No filter\",\n clearable: false,\n required: false,\n disabled: false,\n },\n);\n\nconst selectedDataset = computed<PlRef | undefined>(() => {\n const v = model.value;\n if (v === undefined) return undefined;\n return isPrimaryRef(v) ? v.column : v;\n});\n\nconst selectedFilter = computed<PlRef | undefined>(() => {\n const v = model.value;\n return isPrimaryRef(v) ? v.filter : undefined;\n});\n\nconst currentDatasetOption = computed<DatasetOption | undefined>(() => {\n const dataset = selectedDataset.value;\n if (!dataset) return undefined;\n return props.options?.find((o) => plRefsEqual(o.ref, dataset, true));\n});\n\nconst hasFilters = computed(() => (currentDatasetOption.value?.filters?.length ?? 0) > 0);\n\n/**\n * Filter dropdown options. The first entry (`null`) is the \"No filter\" choice —\n * null distinguishes it from `undefined` (dropdown clear button, disabled here).\n */\nconst filterOptions = computed<ListOption<PlRef | null>[]>(() => {\n const filters = currentDatasetOption.value?.filters;\n if (!filters) return [];\n return [\n { label: props.noFilterLabel, value: null } as ListOption<PlRef | null>,\n ...filters.map((f) => ({ label: f.label, value: f.ref }) as ListOption<PlRef | null>),\n ];\n});\n\nconst filterValue = computed<PlRef | null>(() => selectedFilter.value ?? null);\n\nfunction emitValue(dataset: PlRef | undefined, filter: PlRef | undefined) {\n model.value = dataset === undefined ? undefined : createPrimaryRef(dataset, filter);\n}\n\nfunction onDatasetChange(dataset: PlRef | undefined) {\n emitValue(dataset, undefined);\n}\n\nfunction onFilterChange(value: PlRef | null | undefined) {\n const dataset = selectedDataset.value;\n if (!dataset) return;\n emitValue(dataset, value ?? undefined);\n}\n</script>\n\n<template>\n <div class=\"pl-dataset-selector\">\n <PlDropdownRef\n :model-value=\"selectedDataset\"\n :options=\"options\"\n :label=\"label\"\n :helper=\"helper\"\n :loading-options-helper=\"loadingOptionsHelper\"\n :error=\"error\"\n :placeholder=\"placeholder\"\n :clearable=\"clearable\"\n :required=\"required\"\n :disabled=\"disabled\"\n @update:model-value=\"onDatasetChange\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdownRef>\n <PlDropdown\n v-if=\"hasFilters\"\n :model-value=\"filterValue\"\n :options=\"filterOptions\"\n :label=\"filterLabel\"\n :placeholder=\"filterPlaceholder\"\n :disabled=\"disabled\"\n @update:model-value=\"onFilterChange\"\n />\n </div>\n</template>\n\n<style scoped>\n.pl-dataset-selector {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n</style>\n"],"mappings":""}
@@ -0,0 +1 @@
1
+ .pl-dataset-selector[data-v-b52c3499]{flex-direction:column;gap:12px;display:flex}
@@ -0,0 +1,105 @@
1
+ import { DatasetOption, PlRef, PrimaryRef } from '@platforma-sdk/model';
2
+ /**
3
+ * Select a dataset and (optionally) a filter column, emitting a `PrimaryRef`.
4
+ *
5
+ * Behaves like {@link PlDropdownRef} when none of the offered datasets carry
6
+ * filter options. When the selected dataset has compatible filters, a second
7
+ * dropdown appears with the filters plus a "No filter" entry.
8
+ *
9
+ * Accepts `PrimaryRef | PlRef | undefined` as `modelValue` (a plain `PlRef`
10
+ * is treated as an unfiltered dataset), but always emits `PrimaryRef`
11
+ * (or `undefined` when cleared).
12
+ */
13
+ declare const _default: __VLS_WithTemplateSlots<import('vue').DefineComponent<{
14
+ modelValue?: PrimaryRef | PlRef | undefined;
15
+ } & {
16
+ /** Available datasets, each optionally carrying compatible filter choices. */
17
+ options?: Readonly<DatasetOption[]>;
18
+ /** Label above the dataset dropdown. */
19
+ label?: string;
20
+ /** Helper text below the dataset dropdown (shown when there is no error). */
21
+ helper?: string;
22
+ /** Helper text shown while `options` is undefined (loading). */
23
+ loadingOptionsHelper?: string;
24
+ /** Error message displayed below the dataset dropdown. */
25
+ error?: unknown;
26
+ /** Placeholder when no dataset is selected. */
27
+ placeholder?: string;
28
+ /** Label above the filter dropdown. */
29
+ filterLabel?: string;
30
+ /** Placeholder for the filter dropdown. */
31
+ filterPlaceholder?: string;
32
+ /** Label of the "no filter" entry prepended to the filter options. */
33
+ noFilterLabel?: string;
34
+ /** Show a clear button on the dataset dropdown. */
35
+ clearable?: boolean;
36
+ /** Mark the dataset dropdown as required. */
37
+ required?: boolean;
38
+ /** Disable all interaction. */
39
+ disabled?: boolean;
40
+ }, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
41
+ "update:modelValue": (value: PrimaryRef | Readonly<{
42
+ __isRef: true;
43
+ blockId: string;
44
+ name: string;
45
+ requireEnrichments?: true | undefined;
46
+ }> | undefined) => any;
47
+ }, string, import('vue').PublicProps, Readonly<{
48
+ modelValue?: PrimaryRef | PlRef | undefined;
49
+ } & {
50
+ /** Available datasets, each optionally carrying compatible filter choices. */
51
+ options?: Readonly<DatasetOption[]>;
52
+ /** Label above the dataset dropdown. */
53
+ label?: string;
54
+ /** Helper text below the dataset dropdown (shown when there is no error). */
55
+ helper?: string;
56
+ /** Helper text shown while `options` is undefined (loading). */
57
+ loadingOptionsHelper?: string;
58
+ /** Error message displayed below the dataset dropdown. */
59
+ error?: unknown;
60
+ /** Placeholder when no dataset is selected. */
61
+ placeholder?: string;
62
+ /** Label above the filter dropdown. */
63
+ filterLabel?: string;
64
+ /** Placeholder for the filter dropdown. */
65
+ filterPlaceholder?: string;
66
+ /** Label of the "no filter" entry prepended to the filter options. */
67
+ noFilterLabel?: string;
68
+ /** Show a clear button on the dataset dropdown. */
69
+ clearable?: boolean;
70
+ /** Mark the dataset dropdown as required. */
71
+ required?: boolean;
72
+ /** Disable all interaction. */
73
+ disabled?: boolean;
74
+ }> & Readonly<{
75
+ "onUpdate:modelValue"?: ((value: PrimaryRef | Readonly<{
76
+ __isRef: true;
77
+ blockId: string;
78
+ name: string;
79
+ requireEnrichments?: true | undefined;
80
+ }> | undefined) => any) | undefined;
81
+ }>, {
82
+ error: undefined;
83
+ label: string;
84
+ placeholder: string;
85
+ options: Readonly<DatasetOption[]>;
86
+ helper: string;
87
+ loadingOptionsHelper: string;
88
+ clearable: boolean;
89
+ required: boolean;
90
+ disabled: boolean;
91
+ filterLabel: string;
92
+ filterPlaceholder: string;
93
+ noFilterLabel: string;
94
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>, Readonly<{
95
+ tooltip?: () => unknown;
96
+ }> & {
97
+ tooltip?: () => unknown;
98
+ }>;
99
+ export default _default;
100
+ type __VLS_WithTemplateSlots<T, S> = T & {
101
+ new (): {
102
+ $slots: S;
103
+ };
104
+ };
105
+ //# sourceMappingURL=PlDatasetSelector.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlDatasetSelector.vue.d.ts","sourceRoot":"","sources":["../../../src/components/PlDatasetSelector/PlDatasetSelector.vue"],"names":[],"mappings":"AAsKA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAO7E;;;;;;;;;;GAUG;;iBA0HU,UAAU,GAAG,KAAK,GAAG,SAAS;;IA7GvC,8EAA8E;cACpE,QAAQ,CAAC,aAAa,EAAE,CAAC;IACnC,wCAAwC;YAChC,MAAM;IACd,6EAA6E;aACpE,MAAM;IACf,gEAAgE;2BACzC,MAAM;IAC7B,0DAA0D;YAClD,OAAO;IACf,+CAA+C;kBACjC,MAAM;IACpB,uCAAuC;kBACzB,MAAM;IACpB,2CAA2C;wBACvB,MAAM;IAC1B,sEAAsE;oBACtD,MAAM;IACtB,mDAAmD;gBACvC,OAAO;IACnB,6CAA6C;eAClC,OAAO;IAClB,+BAA+B;eACpB,OAAO;;;;;;;;;iBAsFT,UAAU,GAAG,KAAK,GAAG,SAAS;;IA7GvC,8EAA8E;cACpE,QAAQ,CAAC,aAAa,EAAE,CAAC;IACnC,wCAAwC;YAChC,MAAM;IACd,6EAA6E;aACpE,MAAM;IACf,gEAAgE;2BACzC,MAAM;IAC7B,0DAA0D;YAClD,OAAO;IACf,+CAA+C;kBACjC,MAAM;IACpB,uCAAuC;kBACzB,MAAM;IACpB,2CAA2C;wBACvB,MAAM;IAC1B,sEAAsE;oBACtD,MAAM;IACtB,mDAAmD;gBACvC,OAAO;IACnB,6CAA6C;eAClC,OAAO;IAClB,+BAA+B;eACpB,OAAO;;;;;;;;;;WApBV,MAAM;iBAQA,MAAM;aAVV,QAAQ,CAAC,aAAa,EAAE,CAAC;YAI1B,MAAM;0BAEQ,MAAM;eAYjB,OAAO;cAER,OAAO;cAEP,OAAO;iBAVJ,MAAM;uBAEA,MAAM;mBAEV,MAAM;;cA3Bd,MAAM,OAAO;;cAAb,MAAM,OAAO;;AAFzB,wBAmQK;AAcL,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { computed as e, createBlock as t, createCommentVNode as n, createElementBlock as r, createSlots as i, createVNode as a, defineComponent as o, mergeModels as s, openBlock as c, renderSlot as l, unref as u, useModel as d, useSlots as f, withCtx as p } from "vue";
2
+ import { createPrimaryRef as m, isPrimaryRef as h, plRefsEqual as g } from "@platforma-sdk/model";
3
+ import { PlDropdown as _, PlDropdownRef as v } from "@milaboratories/uikit";
4
+ //#region src/components/PlDatasetSelector/PlDatasetSelector.vue?vue&type=script&setup=true&lang.ts
5
+ var y = { class: "pl-dataset-selector" }, b = /* @__PURE__ */ o({
6
+ name: "PlDatasetSelector",
7
+ props: /* @__PURE__ */ s({
8
+ options: { default: void 0 },
9
+ label: { default: void 0 },
10
+ helper: { default: void 0 },
11
+ loadingOptionsHelper: { default: void 0 },
12
+ error: { default: void 0 },
13
+ placeholder: { default: "..." },
14
+ filterLabel: { default: "" },
15
+ filterPlaceholder: { default: "..." },
16
+ noFilterLabel: { default: "No filter" },
17
+ clearable: {
18
+ type: Boolean,
19
+ default: !1
20
+ },
21
+ required: {
22
+ type: Boolean,
23
+ default: !1
24
+ },
25
+ disabled: {
26
+ type: Boolean,
27
+ default: !1
28
+ }
29
+ }, {
30
+ modelValue: {},
31
+ modelModifiers: {}
32
+ }),
33
+ emits: ["update:modelValue"],
34
+ setup(o) {
35
+ let s = f(), b = d(o, "modelValue"), x = o, S = e(() => {
36
+ let e = b.value;
37
+ if (e !== void 0) return h(e) ? e.column : e;
38
+ }), C = e(() => {
39
+ let e = b.value;
40
+ return h(e) ? e.filter : void 0;
41
+ }), w = e(() => {
42
+ let e = S.value;
43
+ if (e) return x.options?.find((t) => g(t.ref, e, !0));
44
+ }), T = e(() => (w.value?.filters?.length ?? 0) > 0), E = e(() => {
45
+ let e = w.value?.filters;
46
+ return e ? [{
47
+ label: x.noFilterLabel,
48
+ value: null
49
+ }, ...e.map((e) => ({
50
+ label: e.label,
51
+ value: e.ref
52
+ }))] : [];
53
+ }), D = e(() => C.value ?? null);
54
+ function O(e, t) {
55
+ b.value = e === void 0 ? void 0 : m(e, t);
56
+ }
57
+ function k(e) {
58
+ O(e, void 0);
59
+ }
60
+ function A(e) {
61
+ let t = S.value;
62
+ t && O(t, e ?? void 0);
63
+ }
64
+ return (e, d) => (c(), r("div", y, [a(u(v), {
65
+ "model-value": S.value,
66
+ options: o.options,
67
+ label: o.label,
68
+ helper: o.helper,
69
+ "loading-options-helper": o.loadingOptionsHelper,
70
+ error: o.error,
71
+ placeholder: o.placeholder,
72
+ clearable: o.clearable,
73
+ required: o.required,
74
+ disabled: o.disabled,
75
+ "onUpdate:modelValue": k
76
+ }, i({ _: 2 }, [s.tooltip ? {
77
+ name: "tooltip",
78
+ fn: p(() => [l(e.$slots, "tooltip", {}, void 0, !0)]),
79
+ key: "0"
80
+ } : void 0]), 1032, [
81
+ "model-value",
82
+ "options",
83
+ "label",
84
+ "helper",
85
+ "loading-options-helper",
86
+ "error",
87
+ "placeholder",
88
+ "clearable",
89
+ "required",
90
+ "disabled"
91
+ ]), T.value ? (c(), t(u(_), {
92
+ key: 0,
93
+ "model-value": D.value,
94
+ options: E.value,
95
+ label: o.filterLabel,
96
+ placeholder: o.filterPlaceholder,
97
+ disabled: o.disabled,
98
+ "onUpdate:modelValue": A
99
+ }, null, 8, [
100
+ "model-value",
101
+ "options",
102
+ "label",
103
+ "placeholder",
104
+ "disabled"
105
+ ])) : n("", !0)]));
106
+ }
107
+ });
108
+ //#endregion
109
+ export { b as default };
110
+
111
+ //# sourceMappingURL=PlDatasetSelector.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlDatasetSelector.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/PlDatasetSelector/PlDatasetSelector.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Select a dataset and (optionally) a filter column, emitting a `PrimaryRef`.\n *\n * Behaves like {@link PlDropdownRef} when none of the offered datasets carry\n * filter options. When the selected dataset has compatible filters, a second\n * dropdown appears with the filters plus a \"No filter\" entry.\n *\n * Accepts `PrimaryRef | PlRef | undefined` as `modelValue` (a plain `PlRef`\n * is treated as an unfiltered dataset), but always emits `PrimaryRef`\n * (or `undefined` when cleared).\n */\nexport default {\n name: \"PlDatasetSelector\",\n};\n</script>\n\n<script lang=\"ts\" setup>\nimport type { DatasetOption, PlRef, PrimaryRef } from \"@platforma-sdk/model\";\nimport { createPrimaryRef, isPrimaryRef, plRefsEqual } from \"@platforma-sdk/model\";\nimport type { ListOption } from \"@milaboratories/uikit\";\nimport { PlDropdown, PlDropdownRef } from \"@milaboratories/uikit\";\nimport { computed } from \"vue\";\n\nconst slots = defineSlots<{\n tooltip?: () => unknown;\n}>();\n\n/**\n * v-model value. Accepts `PrimaryRef`, plain `PlRef` (treated as unfiltered),\n * or `undefined`. Writes always emit `PrimaryRef` or `undefined`.\n */\nconst model = defineModel<PrimaryRef | PlRef | undefined>();\n\nconst props = withDefaults(\n defineProps<{\n /** Available datasets, each optionally carrying compatible filter choices. */\n options?: Readonly<DatasetOption[]>;\n /** Label above the dataset dropdown. */\n label?: string;\n /** Helper text below the dataset dropdown (shown when there is no error). */\n helper?: string;\n /** Helper text shown while `options` is undefined (loading). */\n loadingOptionsHelper?: string;\n /** Error message displayed below the dataset dropdown. */\n error?: unknown;\n /** Placeholder when no dataset is selected. */\n placeholder?: string;\n /** Label above the filter dropdown. */\n filterLabel?: string;\n /** Placeholder for the filter dropdown. */\n filterPlaceholder?: string;\n /** Label of the \"no filter\" entry prepended to the filter options. */\n noFilterLabel?: string;\n /** Show a clear button on the dataset dropdown. */\n clearable?: boolean;\n /** Mark the dataset dropdown as required. */\n required?: boolean;\n /** Disable all interaction. */\n disabled?: boolean;\n }>(),\n {\n options: undefined,\n label: undefined,\n helper: undefined,\n loadingOptionsHelper: undefined,\n error: undefined,\n placeholder: \"...\",\n filterLabel: \"\",\n filterPlaceholder: \"...\",\n noFilterLabel: \"No filter\",\n clearable: false,\n required: false,\n disabled: false,\n },\n);\n\nconst selectedDataset = computed<PlRef | undefined>(() => {\n const v = model.value;\n if (v === undefined) return undefined;\n return isPrimaryRef(v) ? v.column : v;\n});\n\nconst selectedFilter = computed<PlRef | undefined>(() => {\n const v = model.value;\n return isPrimaryRef(v) ? v.filter : undefined;\n});\n\nconst currentDatasetOption = computed<DatasetOption | undefined>(() => {\n const dataset = selectedDataset.value;\n if (!dataset) return undefined;\n return props.options?.find((o) => plRefsEqual(o.ref, dataset, true));\n});\n\nconst hasFilters = computed(() => (currentDatasetOption.value?.filters?.length ?? 0) > 0);\n\n/**\n * Filter dropdown options. The first entry (`null`) is the \"No filter\" choice —\n * null distinguishes it from `undefined` (dropdown clear button, disabled here).\n */\nconst filterOptions = computed<ListOption<PlRef | null>[]>(() => {\n const filters = currentDatasetOption.value?.filters;\n if (!filters) return [];\n return [\n { label: props.noFilterLabel, value: null } as ListOption<PlRef | null>,\n ...filters.map((f) => ({ label: f.label, value: f.ref }) as ListOption<PlRef | null>),\n ];\n});\n\nconst filterValue = computed<PlRef | null>(() => selectedFilter.value ?? null);\n\nfunction emitValue(dataset: PlRef | undefined, filter: PlRef | undefined) {\n model.value = dataset === undefined ? undefined : createPrimaryRef(dataset, filter);\n}\n\nfunction onDatasetChange(dataset: PlRef | undefined) {\n emitValue(dataset, undefined);\n}\n\nfunction onFilterChange(value: PlRef | null | undefined) {\n const dataset = selectedDataset.value;\n if (!dataset) return;\n emitValue(dataset, value ?? undefined);\n}\n</script>\n\n<template>\n <div class=\"pl-dataset-selector\">\n <PlDropdownRef\n :model-value=\"selectedDataset\"\n :options=\"options\"\n :label=\"label\"\n :helper=\"helper\"\n :loading-options-helper=\"loadingOptionsHelper\"\n :error=\"error\"\n :placeholder=\"placeholder\"\n :clearable=\"clearable\"\n :required=\"required\"\n :disabled=\"disabled\"\n @update:model-value=\"onDatasetChange\"\n >\n <template v-if=\"slots.tooltip\" #tooltip>\n <slot name=\"tooltip\" />\n </template>\n </PlDropdownRef>\n <PlDropdown\n v-if=\"hasFilters\"\n :model-value=\"filterValue\"\n :options=\"filterOptions\"\n :label=\"filterLabel\"\n :placeholder=\"filterPlaceholder\"\n :disabled=\"disabled\"\n @update:model-value=\"onFilterChange\"\n />\n </div>\n</template>\n\n<style scoped>\n.pl-dataset-selector {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n</style>\n"],"mappings":";;;;;CAaE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWR,IAAM,IAAQ,GAEV,EAME,IAAQ,EAA2C,GAAA,aAAE,EAErD,IAAQ,GA2CR,IAAkB,QAAkC;GACxD,IAAM,IAAI,EAAM;AACZ,aAAM,KAAA,EACV,QAAO,EAAa,EAAE,GAAG,EAAE,SAAS;IACpC,EAEI,IAAiB,QAAkC;GACvD,IAAM,IAAI,EAAM;AAChB,UAAO,EAAa,EAAE,GAAG,EAAE,SAAS,KAAA;IACpC,EAEI,IAAuB,QAA0C;GACrE,IAAM,IAAU,EAAgB;AAC3B,SACL,QAAO,EAAM,SAAS,MAAM,MAAM,EAAY,EAAE,KAAK,GAAS,GAAK,CAAC;IACpE,EAEI,IAAa,SAAgB,EAAqB,OAAO,SAAS,UAAU,KAAK,EAAE,EAMnF,IAAgB,QAA2C;GAC/D,IAAM,IAAU,EAAqB,OAAO;AAE5C,UADK,IACE,CACL;IAAE,OAAO,EAAM;IAAe,OAAO;IAAM,EAC3C,GAAG,EAAQ,KAAK,OAAO;IAAE,OAAO,EAAE;IAAO,OAAO,EAAE;IAAK,EAA8B,CACtF,GAJoB,EAAE;IAKvB,EAEI,IAAc,QAA6B,EAAe,SAAS,KAAK;EAE9E,SAAS,EAAU,GAA4B,GAA2B;AACxE,KAAM,QAAQ,MAAY,KAAA,IAAY,KAAA,IAAY,EAAiB,GAAS,EAAO;;EAGrF,SAAS,EAAgB,GAA4B;AACnD,KAAU,GAAS,KAAA,EAAU;;EAG/B,SAAS,EAAe,GAAiC;GACvD,IAAM,IAAU,EAAgB;AAC3B,QACL,EAAU,GAAS,KAAS,KAAA,EAAU;;yBAKtC,EA2BM,OA3BN,GA2BM,CA1BJ,EAgBgB,EAAA,EAAA,EAAA;GAfb,eAAa,EAAA;GACb,SAAS,EAAA;GACT,OAAO,EAAA;GACP,QAAQ,EAAA;GACR,0BAAwB,EAAA;GACxB,OAAO,EAAA;GACP,aAAa,EAAA;GACb,WAAW,EAAA;GACX,UAAU,EAAA;GACV,UAAU,EAAA;GACV,uBAAoB;kBAEL,EAAM,UAAA;SAAU;eACP,CAAvB,EAAuB,EAAA,QAAA,WAAA,EAAA,EAAA,KAAA,GAAA,GAAA,CAAA,CAAA;;;;;;;;;;;;;MAInB,EAAA,SAAA,GAAA,EADR,EAQE,EAAA,EAAA,EAAA;;GANC,eAAa,EAAA;GACb,SAAS,EAAA;GACT,OAAO,EAAA;GACP,aAAa,EAAA;GACb,UAAU,EAAA;GACV,uBAAoB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=PlDatasetSelector.jsdomtest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlDatasetSelector.jsdomtest.d.ts","sourceRoot":"","sources":["../../../../src/components/PlDatasetSelector/__tests__/PlDatasetSelector.jsdomtest.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { default as PlDatasetSelector } from './PlDatasetSelector.vue';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/PlDatasetSelector/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1 @@
1
+ import "./PlDatasetSelector.js";
package/dist/index.js CHANGED
@@ -9,11 +9,11 @@ import x from "./components/PlAgGridColumnManager/PlAgGridColumnManager.js";
9
9
  import S from "./components/PlAdvancedFilter/PlAdvancedFilter.js";
10
10
  import { PlAdvancedFilterSupportedFilters as C } from "./components/PlAdvancedFilter/index.js";
11
11
  import w from "./components/PlTableFilters/PlTableFiltersV2.js";
12
- import { DeferredCircular as T, ensureNodeVisible as ee } from "./components/PlAgDataTable/sources/focus-row.js";
13
- import E from "./components/PlAgColumnHeader/PlAgColumnHeader.js";
14
- import D from "./components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.js";
15
- import { defaultMainMenuItems as O } from "./components/PlAgDataTable/sources/menu-items.js";
16
- import k from "./components/PlAgDataTable/PlAgOverlayLoading.js";
12
+ import { DeferredCircular as T, ensureNodeVisible as E } from "./components/PlAgDataTable/sources/focus-row.js";
13
+ import D from "./components/PlAgColumnHeader/PlAgColumnHeader.js";
14
+ import O from "./components/PlAgTextAndButtonCell/PlAgTextAndButtonCell.js";
15
+ import { defaultMainMenuItems as k } from "./components/PlAgDataTable/sources/menu-items.js";
16
+ import ee from "./components/PlAgDataTable/PlAgOverlayLoading.js";
17
17
  import A from "./components/PlAgDataTable/PlAgOverlayNoRows.js";
18
18
  import { AgGridTheme as j } from "./AgGridVue/AgGridTheme.js";
19
19
  import M from "./components/PlAgDataTable/PlAgDataTableV2.js";
@@ -29,17 +29,18 @@ import { deselectAll as V, getSelectedRowsCount as H, getTotalRowsCount as U, is
29
29
  import K from "./components/PlAnnotations/components/PlAnnotations.js";
30
30
  import q from "./components/PlAnnotations/components/PlAnnotationsModal.js";
31
31
  import J from "./components/PlBtnExportArchive/PlBtnExportArchive.js";
32
- import { usePlugin as Y } from "./usePlugin.js";
33
- import { defineStore as X } from "./defineStore.js";
34
- import { activateAgGrid as Z } from "./composition/AgGrid/index.js";
35
- import { objectHash as Q } from "./objectHash.js";
36
- import { computedResult as $ } from "./computedResult.js";
37
- import { ReactiveFileContent as te } from "./composition/fileContent.js";
32
+ import Y from "./components/PlDatasetSelector/PlDatasetSelector.js";
33
+ import { usePlugin as X } from "./usePlugin.js";
34
+ import { defineStore as Z } from "./defineStore.js";
35
+ import { activateAgGrid as Q } from "./composition/AgGrid/index.js";
36
+ import { objectHash as $ } from "./objectHash.js";
37
+ import { computedResult as te } from "./computedResult.js";
38
+ import { ReactiveFileContent as ne } from "./composition/fileContent.js";
38
39
  import "./lib.js";
39
- import { setAutoFreeze as ne } from "immer";
40
+ import { setAutoFreeze as re } from "immer";
40
41
  export * from "@milaboratories/uikit";
41
- ne(!1);
42
+ re(!1);
42
43
  //#endregion
43
- export { j as AgGridTheme, g as BlockLayout, T as DeferredCircular, e as MultiError, S as PlAdvancedFilterComponent, C as PlAdvancedFilterSupportedFilters, I as PlAgCellFile, P as PlAgCellProgress, z as PlAgCellStatusTag, R as PlAgChartHistogramCell, L as PlAgChartStackedBarCell, E as PlAgColumnHeader, _ as PlAgCsvExporter, v as PlAgDataTableRowNumberColId, M as PlAgDataTableV2, x as PlAgGridColumnManager, k as PlAgOverlayLoading, A as PlAgOverlayNoRows, D as PlAgTextAndButtonCell, K as PlAnnotations, q as PlAnnotationsModal, J as PlBtnExportArchive, M as PlDataTableV2, w as PlTableFiltersV2, te as ReactiveFileContent, t as UnresolvedError, Z as activateAgGrid, y as autoSizeRowNumberColumn, $ as computedResult, F as createAgGridColDef, u as createModel, O as defaultMainMenuItems, d as defineApp, f as defineAppV3, X as defineStore, V as deselectAll, n as ensureError, ee as ensureNodeVisible, r as ensureOutputHasStableFlag, i as formatZodError, H as getSelectedRowsCount, U as getTotalRowsCount, a as identity, o as isDefined, W as isSelectionEnabled, s as isZodError, b as makeRowNumberColDef, Q as objectHash, p as pluginDataKey, G as selectAll, c as unwrapOutput, B as useAgGridOptions, m as useFeatureFlags, N as usePlDataTableSettingsV2, Y as usePlugin, h as useSdkPlugin, l as wrapOptionalResult };
44
+ export { j as AgGridTheme, g as BlockLayout, T as DeferredCircular, e as MultiError, S as PlAdvancedFilterComponent, C as PlAdvancedFilterSupportedFilters, I as PlAgCellFile, P as PlAgCellProgress, z as PlAgCellStatusTag, R as PlAgChartHistogramCell, L as PlAgChartStackedBarCell, D as PlAgColumnHeader, _ as PlAgCsvExporter, v as PlAgDataTableRowNumberColId, M as PlAgDataTableV2, x as PlAgGridColumnManager, ee as PlAgOverlayLoading, A as PlAgOverlayNoRows, O as PlAgTextAndButtonCell, K as PlAnnotations, q as PlAnnotationsModal, J as PlBtnExportArchive, M as PlDataTableV2, Y as PlDatasetSelector, w as PlTableFiltersV2, ne as ReactiveFileContent, t as UnresolvedError, Q as activateAgGrid, y as autoSizeRowNumberColumn, te as computedResult, F as createAgGridColDef, u as createModel, k as defaultMainMenuItems, d as defineApp, f as defineAppV3, Z as defineStore, V as deselectAll, n as ensureError, E as ensureNodeVisible, r as ensureOutputHasStableFlag, i as formatZodError, H as getSelectedRowsCount, U as getTotalRowsCount, a as identity, o as isDefined, W as isSelectionEnabled, s as isZodError, b as makeRowNumberColDef, $ as objectHash, p as pluginDataKey, G as selectAll, c as unwrapOutput, B as useAgGridOptions, m as useFeatureFlags, N as usePlDataTableSettingsV2, X as usePlugin, h as useSdkPlugin, l as wrapOptionalResult };
44
45
 
45
46
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { setAutoFreeze } from \"immer\";\n\nsetAutoFreeze(false);\n\nexport * from \"./lib\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,GAAc,GAAM"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { setAutoFreeze } from \"immer\";\n\nsetAutoFreeze(false);\n\nexport * from \"./lib\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,GAAc,GAAM"}
package/dist/lib.d.ts CHANGED
@@ -18,6 +18,7 @@ export * from './components/PlTableFilters';
18
18
  export * from './components/PlAnnotations';
19
19
  export * from './components/PlBtnExportArchive';
20
20
  export * from './components/PlAdvancedFilter';
21
+ export * from './components/PlDatasetSelector';
21
22
  export * from './defineApp';
22
23
  export { usePlugin } from './usePlugin';
23
24
  export type { PluginState } from './usePlugin';
package/dist/lib.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAC5F,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AAClG,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,kDAAkD,CAAC;AAEhG,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,cAAc,aAAa,CAAC;AAE5B,cAAc,+BAA+B,CAAC;AAE9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AAErD,cAAc,4BAA4B,CAAC;AAE3C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,oCAAoC,CAAC;AAEnD,cAAc,6BAA6B,CAAC;AAE5C,cAAc,4BAA4B,CAAC;AAE3C,cAAc,iCAAiC,CAAC;AAEhD,cAAc,+BAA+B,CAAC;AAE9C,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,cAAc,eAAe,CAAC;AAE9B,cAAc,SAAS,CAAC;AAExB,cAAc,eAAe,CAAC;AAE9B,cAAc,sBAAsB,CAAC;AAErC,cAAc,SAAS,CAAC;AAExB,cAAc,cAAc,CAAC;AAE7B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uBAAuB,CAAC;AAEtC,mBAAmB,uBAAuB,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAC5F,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AAClG,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,kDAAkD,CAAC;AAEhG,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,cAAc,aAAa,CAAC;AAE5B,cAAc,+BAA+B,CAAC;AAE9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AAErD,cAAc,4BAA4B,CAAC;AAE3C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,oCAAoC,CAAC;AAEnD,cAAc,6BAA6B,CAAC;AAE5C,cAAc,4BAA4B,CAAC;AAE3C,cAAc,iCAAiC,CAAC;AAEhD,cAAc,+BAA+B,CAAC;AAE9C,cAAc,gCAAgC,CAAC;AAE/C,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,cAAc,eAAe,CAAC;AAE9B,cAAc,SAAS,CAAC;AAExB,cAAc,eAAe,CAAC;AAE9B,cAAc,sBAAsB,CAAC;AAErC,cAAc,SAAS,CAAC;AAExB,cAAc,cAAc,CAAC;AAE7B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uBAAuB,CAAC;AAEtC,mBAAmB,uBAAuB,CAAC"}
package/dist/lib.js CHANGED
@@ -44,6 +44,8 @@ import "./components/PlAnnotations/components/PlAnnotationsModal.js";
44
44
  import "./components/PlAnnotations/index.js";
45
45
  import "./components/PlBtnExportArchive/PlBtnExportArchive.js";
46
46
  import "./components/PlBtnExportArchive/index.js";
47
+ import "./components/PlDatasetSelector/PlDatasetSelector.js";
48
+ import "./components/PlDatasetSelector/index.js";
47
49
  import "./usePlugin.js";
48
50
  import "./defineStore.js";
49
51
  import "./composition/AgGrid/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.66.2",
3
+ "version": "1.67.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -26,10 +26,10 @@
26
26
  "lru-cache": "^11.2.2",
27
27
  "vue": "^3.5.24",
28
28
  "zod": "~3.25.76",
29
- "@milaboratories/pl-model-common": "1.34.1",
30
- "@milaboratories/pf-spec-driver": "1.3.2",
31
- "@milaboratories/uikit": "2.12.8",
32
- "@platforma-sdk/model": "1.66.2"
29
+ "@milaboratories/pf-spec-driver": "1.3.3",
30
+ "@milaboratories/pl-model-common": "1.35.0",
31
+ "@platforma-sdk/model": "1.67.0",
32
+ "@milaboratories/uikit": "2.12.9"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@faker-js/faker": "^9.2.0",
@@ -37,16 +37,18 @@
37
37
  "@vitest/coverage-istanbul": "^4.1.3",
38
38
  "@vue/test-utils": "^2.4.6",
39
39
  "fast-json-patch": "^3.1.1",
40
+ "jsdom": "^25.0.1",
41
+ "resize-observer-polyfill": "^1.5.1",
40
42
  "rollup-plugin-sourcemaps2": "^0.5.2",
41
43
  "sass": "~1.83.4",
42
44
  "semver": "^7.7.2",
43
45
  "typescript": "~5.9.3",
44
46
  "vite": "^8.0.6",
45
47
  "vitest": "^4.1.3",
46
- "@milaboratories/ts-builder": "1.3.2",
47
48
  "@milaboratories/helpers": "1.14.1",
49
+ "@milaboratories/build-configs": "2.0.0",
48
50
  "@milaboratories/ts-configs": "1.2.3",
49
- "@milaboratories/build-configs": "2.0.0"
51
+ "@milaboratories/ts-builder": "1.3.2"
50
52
  },
51
53
  "scripts": {
52
54
  "dev": "ts-builder serve --target browser-lib",
@@ -0,0 +1,10 @@
1
+ import ResizeObserver from "resize-observer-polyfill";
2
+ globalThis.ResizeObserver = ResizeObserver;
3
+
4
+ // CSS.paintWorklet is not available in jsdom — uikit's PlPlaceholder
5
+ // accesses it at module load time.
6
+ if (typeof globalThis.CSS === "undefined") {
7
+ (globalThis as Record<string, unknown>).CSS = { paintWorklet: { addModule: () => {} } };
8
+ } else if (!("paintWorklet" in globalThis.CSS)) {
9
+ (globalThis.CSS as Record<string, unknown>).paintWorklet = { addModule: () => {} };
10
+ }
@@ -0,0 +1,164 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Select a dataset and (optionally) a filter column, emitting a `PrimaryRef`.
4
+ *
5
+ * Behaves like {@link PlDropdownRef} when none of the offered datasets carry
6
+ * filter options. When the selected dataset has compatible filters, a second
7
+ * dropdown appears with the filters plus a "No filter" entry.
8
+ *
9
+ * Accepts `PrimaryRef | PlRef | undefined` as `modelValue` (a plain `PlRef`
10
+ * is treated as an unfiltered dataset), but always emits `PrimaryRef`
11
+ * (or `undefined` when cleared).
12
+ */
13
+ export default {
14
+ name: "PlDatasetSelector",
15
+ };
16
+ </script>
17
+
18
+ <script lang="ts" setup>
19
+ import type { DatasetOption, PlRef, PrimaryRef } from "@platforma-sdk/model";
20
+ import { createPrimaryRef, isPrimaryRef, plRefsEqual } from "@platforma-sdk/model";
21
+ import type { ListOption } from "@milaboratories/uikit";
22
+ import { PlDropdown, PlDropdownRef } from "@milaboratories/uikit";
23
+ import { computed } from "vue";
24
+
25
+ const slots = defineSlots<{
26
+ tooltip?: () => unknown;
27
+ }>();
28
+
29
+ /**
30
+ * v-model value. Accepts `PrimaryRef`, plain `PlRef` (treated as unfiltered),
31
+ * or `undefined`. Writes always emit `PrimaryRef` or `undefined`.
32
+ */
33
+ const model = defineModel<PrimaryRef | PlRef | undefined>();
34
+
35
+ const props = withDefaults(
36
+ defineProps<{
37
+ /** Available datasets, each optionally carrying compatible filter choices. */
38
+ options?: Readonly<DatasetOption[]>;
39
+ /** Label above the dataset dropdown. */
40
+ label?: string;
41
+ /** Helper text below the dataset dropdown (shown when there is no error). */
42
+ helper?: string;
43
+ /** Helper text shown while `options` is undefined (loading). */
44
+ loadingOptionsHelper?: string;
45
+ /** Error message displayed below the dataset dropdown. */
46
+ error?: unknown;
47
+ /** Placeholder when no dataset is selected. */
48
+ placeholder?: string;
49
+ /** Label above the filter dropdown. */
50
+ filterLabel?: string;
51
+ /** Placeholder for the filter dropdown. */
52
+ filterPlaceholder?: string;
53
+ /** Label of the "no filter" entry prepended to the filter options. */
54
+ noFilterLabel?: string;
55
+ /** Show a clear button on the dataset dropdown. */
56
+ clearable?: boolean;
57
+ /** Mark the dataset dropdown as required. */
58
+ required?: boolean;
59
+ /** Disable all interaction. */
60
+ disabled?: boolean;
61
+ }>(),
62
+ {
63
+ options: undefined,
64
+ label: undefined,
65
+ helper: undefined,
66
+ loadingOptionsHelper: undefined,
67
+ error: undefined,
68
+ placeholder: "...",
69
+ filterLabel: "",
70
+ filterPlaceholder: "...",
71
+ noFilterLabel: "No filter",
72
+ clearable: false,
73
+ required: false,
74
+ disabled: false,
75
+ },
76
+ );
77
+
78
+ const selectedDataset = computed<PlRef | undefined>(() => {
79
+ const v = model.value;
80
+ if (v === undefined) return undefined;
81
+ return isPrimaryRef(v) ? v.column : v;
82
+ });
83
+
84
+ const selectedFilter = computed<PlRef | undefined>(() => {
85
+ const v = model.value;
86
+ return isPrimaryRef(v) ? v.filter : undefined;
87
+ });
88
+
89
+ const currentDatasetOption = computed<DatasetOption | undefined>(() => {
90
+ const dataset = selectedDataset.value;
91
+ if (!dataset) return undefined;
92
+ return props.options?.find((o) => plRefsEqual(o.ref, dataset, true));
93
+ });
94
+
95
+ const hasFilters = computed(() => (currentDatasetOption.value?.filters?.length ?? 0) > 0);
96
+
97
+ /**
98
+ * Filter dropdown options. The first entry (`null`) is the "No filter" choice —
99
+ * null distinguishes it from `undefined` (dropdown clear button, disabled here).
100
+ */
101
+ const filterOptions = computed<ListOption<PlRef | null>[]>(() => {
102
+ const filters = currentDatasetOption.value?.filters;
103
+ if (!filters) return [];
104
+ return [
105
+ { label: props.noFilterLabel, value: null } as ListOption<PlRef | null>,
106
+ ...filters.map((f) => ({ label: f.label, value: f.ref }) as ListOption<PlRef | null>),
107
+ ];
108
+ });
109
+
110
+ const filterValue = computed<PlRef | null>(() => selectedFilter.value ?? null);
111
+
112
+ function emitValue(dataset: PlRef | undefined, filter: PlRef | undefined) {
113
+ model.value = dataset === undefined ? undefined : createPrimaryRef(dataset, filter);
114
+ }
115
+
116
+ function onDatasetChange(dataset: PlRef | undefined) {
117
+ emitValue(dataset, undefined);
118
+ }
119
+
120
+ function onFilterChange(value: PlRef | null | undefined) {
121
+ const dataset = selectedDataset.value;
122
+ if (!dataset) return;
123
+ emitValue(dataset, value ?? undefined);
124
+ }
125
+ </script>
126
+
127
+ <template>
128
+ <div class="pl-dataset-selector">
129
+ <PlDropdownRef
130
+ :model-value="selectedDataset"
131
+ :options="options"
132
+ :label="label"
133
+ :helper="helper"
134
+ :loading-options-helper="loadingOptionsHelper"
135
+ :error="error"
136
+ :placeholder="placeholder"
137
+ :clearable="clearable"
138
+ :required="required"
139
+ :disabled="disabled"
140
+ @update:model-value="onDatasetChange"
141
+ >
142
+ <template v-if="slots.tooltip" #tooltip>
143
+ <slot name="tooltip" />
144
+ </template>
145
+ </PlDropdownRef>
146
+ <PlDropdown
147
+ v-if="hasFilters"
148
+ :model-value="filterValue"
149
+ :options="filterOptions"
150
+ :label="filterLabel"
151
+ :placeholder="filterPlaceholder"
152
+ :disabled="disabled"
153
+ @update:model-value="onFilterChange"
154
+ />
155
+ </div>
156
+ </template>
157
+
158
+ <style scoped>
159
+ .pl-dataset-selector {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 12px;
163
+ }
164
+ </style>
@@ -0,0 +1,257 @@
1
+ import { flushPromises, mount } from "@vue/test-utils";
2
+ import type { DatasetOption, PrimaryRef } from "@platforma-sdk/model";
3
+ import { createPlRef, createPrimaryRef } from "@platforma-sdk/model";
4
+ import { describe, expect, it } from "vitest";
5
+ import PlDatasetSelector from "../PlDatasetSelector.vue";
6
+
7
+ const datasetA = createPlRef("1", "out-a", true);
8
+ const datasetB = createPlRef("2", "out-b", true);
9
+ const filterA1 = createPlRef("1", "filter-a1");
10
+ const filterA2 = createPlRef("1", "filter-a2");
11
+
12
+ const optionsWithFilters: DatasetOption[] = [
13
+ {
14
+ label: "Dataset A",
15
+ ref: datasetA,
16
+ filters: [
17
+ { label: "Top 1000", ref: filterA1 },
18
+ { label: "High quality", ref: filterA2 },
19
+ ],
20
+ },
21
+ // Dataset B has no filters — filter dropdown must stay hidden.
22
+ { label: "Dataset B", ref: datasetB },
23
+ ];
24
+
25
+ const datasetC = createPlRef("3", "out-c", true);
26
+
27
+ const optionsNoFilters: DatasetOption[] = [{ label: "Dataset B", ref: datasetB }];
28
+
29
+ const optionsWithEmptyFilters: DatasetOption[] = [
30
+ { label: "Dataset C", ref: datasetC, filters: [] },
31
+ ];
32
+
33
+ async function pickOption(index: number) {
34
+ const options = [...document.body.querySelectorAll(".dropdown-list-item")] as HTMLElement[];
35
+ options[index].click();
36
+ await flushPromises();
37
+ }
38
+
39
+ describe("PlDatasetSelector", () => {
40
+ it("renders a single dropdown when no dataset has filters", async () => {
41
+ const wrapper = mount(PlDatasetSelector, {
42
+ props: { modelValue: undefined, options: optionsNoFilters },
43
+ attachTo: document.body,
44
+ });
45
+ await flushPromises();
46
+
47
+ const selector = wrapper.find(".pl-dataset-selector");
48
+ expect(selector.exists()).toBe(true);
49
+ // Only PlDropdownRef is rendered — no filter dropdown.
50
+ expect(selector.element.children.length).toBe(1);
51
+ wrapper.unmount();
52
+ });
53
+
54
+ it("shows the filter dropdown when the selected dataset has filters", async () => {
55
+ const wrapper = mount(PlDatasetSelector, {
56
+ props: { modelValue: createPrimaryRef(datasetA), options: optionsWithFilters },
57
+ attachTo: document.body,
58
+ });
59
+ await flushPromises();
60
+
61
+ expect(wrapper.find(".pl-dataset-selector").element.children.length).toBe(2);
62
+ wrapper.unmount();
63
+ });
64
+
65
+ it("hides the filter dropdown when the selected dataset has no filters", async () => {
66
+ const wrapper = mount(PlDatasetSelector, {
67
+ props: { modelValue: createPrimaryRef(datasetB), options: optionsWithFilters },
68
+ attachTo: document.body,
69
+ });
70
+ await flushPromises();
71
+
72
+ expect(wrapper.find(".pl-dataset-selector").element.children.length).toBe(1);
73
+ wrapper.unmount();
74
+ });
75
+
76
+ it("emits PrimaryRef with filter: undefined when dataset changes", async () => {
77
+ const wrapper = mount(PlDatasetSelector, {
78
+ props: {
79
+ modelValue: createPrimaryRef(datasetA, filterA1),
80
+ options: optionsWithFilters,
81
+ "onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
82
+ },
83
+ attachTo: document.body,
84
+ });
85
+ await flushPromises();
86
+
87
+ // Open the dataset dropdown (the first input — dataset comes first).
88
+ const inputs = wrapper.findAll("input");
89
+ await inputs[0].trigger("focus");
90
+
91
+ // Dataset A is already selected (index 0); pick Dataset B (index 1).
92
+ await pickOption(1);
93
+
94
+ const emitted = wrapper.emitted("update:modelValue");
95
+ expect(emitted).toBeDefined();
96
+ const last = emitted![emitted!.length - 1][0] as PrimaryRef;
97
+ expect(last).toEqual({ __isPrimaryRef: "v1", column: datasetB });
98
+ wrapper.unmount();
99
+ });
100
+
101
+ it("emits PrimaryRef with filter set when a filter is picked", async () => {
102
+ const wrapper = mount(PlDatasetSelector, {
103
+ props: {
104
+ modelValue: createPrimaryRef(datasetA),
105
+ options: optionsWithFilters,
106
+ "onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
107
+ },
108
+ attachTo: document.body,
109
+ });
110
+ await flushPromises();
111
+
112
+ // Open the filter dropdown — it's the second input in the component.
113
+ const inputs = wrapper.findAll("input");
114
+ expect(inputs.length).toBe(2);
115
+ await inputs[1].trigger("focus");
116
+
117
+ // Options are: [No filter, Top 1000, High quality]. Pick "Top 1000" (index 1).
118
+ await pickOption(1);
119
+
120
+ const emitted = wrapper.emitted("update:modelValue");
121
+ expect(emitted).toBeDefined();
122
+ const last = emitted![emitted!.length - 1][0] as PrimaryRef;
123
+ expect(last).toEqual({ __isPrimaryRef: "v1", column: datasetA, filter: filterA1 });
124
+ wrapper.unmount();
125
+ });
126
+
127
+ it("emits PrimaryRef with filter: undefined when 'No filter' is picked", async () => {
128
+ const wrapper = mount(PlDatasetSelector, {
129
+ props: {
130
+ modelValue: createPrimaryRef(datasetA, filterA1),
131
+ options: optionsWithFilters,
132
+ "onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
133
+ },
134
+ attachTo: document.body,
135
+ });
136
+ await flushPromises();
137
+
138
+ const inputs = wrapper.findAll("input");
139
+ await inputs[1].trigger("focus");
140
+ // Pick "No filter" (index 0).
141
+ await pickOption(0);
142
+
143
+ const emitted = wrapper.emitted("update:modelValue");
144
+ expect(emitted).toBeDefined();
145
+ const last = emitted![emitted!.length - 1][0] as PrimaryRef;
146
+ expect(last).toEqual({ __isPrimaryRef: "v1", column: datasetA });
147
+ expect("filter" in last).toBe(false);
148
+ wrapper.unmount();
149
+ });
150
+
151
+ it("accepts plain PlRef as modelValue for backward compat (filterless dataset)", async () => {
152
+ const wrapper = mount(PlDatasetSelector, {
153
+ props: { modelValue: datasetB, options: optionsWithFilters },
154
+ attachTo: document.body,
155
+ });
156
+ await flushPromises();
157
+
158
+ expect(wrapper.find(".pl-dataset-selector").element.children.length).toBe(1);
159
+ wrapper.unmount();
160
+ });
161
+
162
+ it("accepts plain PlRef as modelValue for backward compat (dataset with filters)", async () => {
163
+ const wrapper = mount(PlDatasetSelector, {
164
+ props: { modelValue: datasetA, options: optionsWithFilters },
165
+ attachTo: document.body,
166
+ });
167
+ await flushPromises();
168
+
169
+ // PlRef matching dataset A — filter dropdown should appear since A has filters.
170
+ expect(wrapper.find(".pl-dataset-selector").element.children.length).toBe(2);
171
+ wrapper.unmount();
172
+ });
173
+
174
+ it("hides filter dropdown when dataset has filters: [] (empty array)", async () => {
175
+ const wrapper = mount(PlDatasetSelector, {
176
+ props: { modelValue: createPrimaryRef(datasetC), options: optionsWithEmptyFilters },
177
+ attachTo: document.body,
178
+ });
179
+ await flushPromises();
180
+
181
+ expect(wrapper.find(".pl-dataset-selector").element.children.length).toBe(1);
182
+ wrapper.unmount();
183
+ });
184
+
185
+ it("filter dropdown defaults to 'No filter' when dataset has filters but none selected", async () => {
186
+ const wrapper = mount(PlDatasetSelector, {
187
+ props: {
188
+ modelValue: createPrimaryRef(datasetA),
189
+ options: optionsWithFilters,
190
+ "onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
191
+ },
192
+ attachTo: document.body,
193
+ });
194
+ await flushPromises();
195
+
196
+ // No emission on mount — the component does not auto-select a filter.
197
+ expect(wrapper.emitted("update:modelValue")).toBeUndefined();
198
+
199
+ // Filter dropdown is visible.
200
+ const inputs = wrapper.findAll("input");
201
+ expect(inputs.length).toBe(2);
202
+
203
+ // Open filter dropdown and verify "No filter" is the first option.
204
+ await inputs[1].trigger("focus");
205
+ const items = document.body.querySelectorAll(".dropdown-list-item");
206
+ expect(items.length).toBe(3); // No filter, Top 1000, High quality
207
+ expect(items[0].textContent).toContain("No filter");
208
+ wrapper.unmount();
209
+ });
210
+
211
+ it("emits PrimaryRef without filter key when selecting a filterless dataset", async () => {
212
+ const wrapper = mount(PlDatasetSelector, {
213
+ props: {
214
+ modelValue: undefined,
215
+ options: optionsNoFilters,
216
+ "onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
217
+ },
218
+ attachTo: document.body,
219
+ });
220
+ await flushPromises();
221
+
222
+ const inputs = wrapper.findAll("input");
223
+ await inputs[0].trigger("focus");
224
+ await pickOption(0);
225
+
226
+ const emitted = wrapper.emitted("update:modelValue");
227
+ expect(emitted).toBeDefined();
228
+ const last = emitted![emitted!.length - 1][0] as PrimaryRef;
229
+ expect(last).toEqual({ __isPrimaryRef: "v1", column: datasetB });
230
+ expect("filter" in last).toBe(false);
231
+ wrapper.unmount();
232
+ });
233
+
234
+ it("emits undefined when cleared via the dataset dropdown", async () => {
235
+ const wrapper = mount(PlDatasetSelector, {
236
+ props: {
237
+ modelValue: createPrimaryRef(datasetA, filterA1),
238
+ options: optionsWithFilters,
239
+ clearable: true,
240
+ "onUpdate:modelValue": (e) => wrapper.setProps({ modelValue: e }),
241
+ },
242
+ attachTo: document.body,
243
+ });
244
+ await flushPromises();
245
+
246
+ // PlDropdown's clear button carries the ".clear" class.
247
+ const clearBtn = wrapper.find(".clear");
248
+ expect(clearBtn.exists()).toBe(true);
249
+ await clearBtn.trigger("click");
250
+
251
+ const emitted = wrapper.emitted("update:modelValue");
252
+ expect(emitted).toBeDefined();
253
+ const last = emitted![emitted!.length - 1][0];
254
+ expect(last).toBeUndefined();
255
+ wrapper.unmount();
256
+ });
257
+ });
@@ -0,0 +1 @@
1
+ export { default as PlDatasetSelector } from "./PlDatasetSelector.vue";
package/src/lib.ts CHANGED
@@ -33,6 +33,8 @@ export * from "./components/PlBtnExportArchive";
33
33
 
34
34
  export * from "./components/PlAdvancedFilter";
35
35
 
36
+ export * from "./components/PlDatasetSelector";
37
+
36
38
  export * from "./defineApp";
37
39
 
38
40
  export { usePlugin } from "./usePlugin";
package/vitest.config.mts CHANGED
@@ -3,8 +3,30 @@ import { defineConfig } from "vitest/config";
3
3
 
4
4
  export default defineConfig(
5
5
  createVitestVueConfig({
6
+ resolve: {
7
+ conditions: ["sources"],
8
+ },
6
9
  test: {
7
10
  includeSource: ["src/**/*.{js,ts}"],
11
+ setupFiles: ["./src/__tests__/setup.ts"],
12
+ projects: [
13
+ {
14
+ extends: true,
15
+ test: {
16
+ name: "node",
17
+ environment: "node",
18
+ include: ["src/**/*.test.ts"],
19
+ },
20
+ },
21
+ {
22
+ extends: true,
23
+ test: {
24
+ name: "jsdom",
25
+ environment: "jsdom",
26
+ include: ["src/**/*.jsdomtest.ts"],
27
+ },
28
+ },
29
+ ],
8
30
  },
9
31
  }),
10
32
  );