+
{{ count }}
import { FormCheckbox } from '@speckle/ui-components'
+import { useFilterUtilities } from '~~/lib/viewer/composables/filtering'
+import { isStringFilter, type FilterData } from '~/lib/viewer/helpers/filters/types'
-defineProps<{
- filterId: string
+const props = defineProps<{
+ filter: FilterData
value: string
- isSelected: boolean
- count: number
- color?: string | null
- isDefaultSelected?: boolean
}>()
defineEmits<{
toggle: []
}>()
+
+const {
+ isActiveFilterValueSelected,
+ getFilterValueColor,
+ getPropertyValueCounts,
+ filters
+} = useFilterUtilities()
+
+const isSelected = computed(() =>
+ isActiveFilterValueSelected(props.filter.id, props.value)
+)
+
+const count = computed(() => {
+ if (!props.filter.filter?.key) return null
+ const counts = getPropertyValueCounts(props.filter.filter.key)
+ return counts[props.value] || 0
+})
+
+const color = computed(() => {
+ if (filters.activeColorFilterId.value !== props.filter.id) {
+ return null
+ }
+ return getFilterValueColor(props.value)
+})
+
+const isDefaultSelected = computed(() => {
+ return (
+ isStringFilter(props.filter) &&
+ props.filter.isDefaultAllSelected &&
+ isSelected.value
+ )
+})
diff --git a/packages/frontend-2/lib/viewer/composables/filtering.ts b/packages/frontend-2/lib/viewer/composables/filtering.ts
index 594ed3117..37fa097ba 100644
--- a/packages/frontend-2/lib/viewer/composables/filtering.ts
+++ b/packages/frontend-2/lib/viewer/composables/filtering.ts
@@ -406,7 +406,9 @@ function createFilteringDataStore() {
currentFilterLogic,
setGhostMode,
ghostMode,
- dataSlices
+ dataSlices,
+ dataSources,
+ buildPropertyIndex
}
}
@@ -534,6 +536,43 @@ export function useFilterUtilities(
return []
}
+ /**
+ * Gets counts for all values of a property at once (performance optimized)
+ */
+ const getPropertyValueCounts = (propertyKey: string): Record => {
+ const valueCounts: Record = {}
+
+ for (const dataSource of dataStore.dataSources.value) {
+ const propertyIndex = dataStore.buildPropertyIndex(dataSource, propertyKey)
+
+ for (const [value, objectIds] of Object.entries(propertyIndex)) {
+ if (!valueCounts[value]) {
+ valueCounts[value] = 0
+ }
+ valueCounts[value] += objectIds.length
+ }
+ }
+
+ return valueCounts
+ }
+
+ /**
+ * Gets the count of objects that have a specific value for a property
+ * Note: For better performance when getting multiple counts, use getPropertyValueCounts
+ */
+ const getPropertyValueCount = (propertyKey: string, value: string): number => {
+ let totalCount = 0
+
+ for (const dataSource of dataStore.dataSources.value) {
+ const propertyIndex = dataStore.buildPropertyIndex(dataSource, propertyKey)
+ if (propertyIndex && propertyIndex[value]) {
+ totalCount += propertyIndex[value].length
+ }
+ }
+
+ return totalCount
+ }
+
/**
* Creates a properly typed FilterData object from PropertyInfo
*/
@@ -1204,6 +1243,50 @@ export function useFilterUtilities(
return color.startsWith('#') ? color : `#${color}`
}
+ /**
+ * Gets filtered and sorted values for a string filter with search and sorting options
+ */
+ const getFilteredFilterValues = (
+ filter: PropertyInfo,
+ options?: {
+ searchQuery?: string
+ sortMode?: 'alphabetical' | 'selected-first'
+ filterId?: string
+ }
+ ): string[] => {
+ const { searchQuery, sortMode = 'alphabetical', filterId } = options || {}
+
+ let values = getAvailableFilterValues(filter)
+
+ // Apply search filtering
+ if (searchQuery?.trim()) {
+ const searchTerm = searchQuery.toLowerCase().trim()
+ values = values.filter((value: string) =>
+ value.toLowerCase().includes(searchTerm)
+ )
+ }
+
+ // Apply sorting
+ if (sortMode === 'selected-first' && filterId) {
+ // Sort: selected first, then alphabetical
+ const selectedValues = values.filter((value: string) =>
+ isActiveFilterValueSelected(filterId, value)
+ )
+ const unselectedValues = values.filter(
+ (value: string) => !isActiveFilterValueSelected(filterId, value)
+ )
+
+ // Sort each group alphabetically
+ const sortedSelectedValues = selectedValues.sort((a, b) => a.localeCompare(b))
+ const sortedUnselectedValues = unselectedValues.sort((a, b) => a.localeCompare(b))
+
+ return [...sortedSelectedValues, ...sortedUnselectedValues]
+ } else {
+ // Sort: pure alphabetical
+ return values.sort((a, b) => a.localeCompare(b))
+ }
+ }
+
return {
isolateObjects,
unIsolateObjects,
@@ -1211,6 +1294,8 @@ export function useFilterUtilities(
showObjects,
filters,
getAvailableFilterValues,
+ getPropertyValueCount,
+ getPropertyValueCounts,
addActiveFilter,
updateFilterProperty,
removeActiveFilter,
@@ -1243,6 +1328,8 @@ export function useFilterUtilities(
toggleColorFilter,
getFilterColorGroups,
getFilterValueColor,
+ // Filtered values
+ getFilteredFilterValues,
// Numeric range filtering
setNumericRange,
// Filter logic