+ * ```
+ */
+export function useSearchFilter(
+ urlParam: string,
+ options: UseSearchFilterOptions = {},
+): Ref
{
+ const context = inject(searchFilterContextKey)
+ if (!context) {
+ throw new Error(
+ `useSearchFilter("${urlParam}") must be used inside a component.`,
+ )
+ }
+
+ const { apiParam = urlParam, defaultValue = undefined } = options
+
+ const value = useRouteQuery(urlParam, defaultValue)
+
+ // Register in onMounted to avoid SSR/hydration mismatch: the registry must be
+ // empty during SSR so server and client produce the same initial HTML.
+ onMounted(() => {
+ context.register(urlParam, { apiParam, ref: value, defaultValue })
+ })
+
+ onScopeDispose(() => {
+ value.value = defaultValue
+ context.unregister(urlParam)
+ })
+
+ return value
+}
diff --git a/datagouv-components/src/composables/useStableQueryParams.ts b/datagouv-components/src/composables/useStableQueryParams.ts
index 5c4af427b..1a06d40b9 100644
--- a/datagouv-components/src/composables/useStableQueryParams.ts
+++ b/datagouv-components/src/composables/useStableQueryParams.ts
@@ -1,11 +1,13 @@
-import { ref, watch, type Ref } from 'vue'
-import type { SearchTypeConfig } from '../types/search'
+import { computed, ref, watch, type Ref } from 'vue'
+import { SearchFilterAliases, type SearchTypeConfig } from '../types/search'
+import type { CustomFilterEntry } from './useSearchFilter'
type FilterRefs = Record>
interface StableQueryParamsOptions {
typeConfig: SearchTypeConfig | undefined
allFilters: FilterRefs
+ customFilterRegistry: Map
q: Ref
sort: Ref
page: Ref
@@ -17,7 +19,7 @@ interface StableQueryParamsOptions {
* Applies hiddenFilters first, then user filters (which can override hiddenFilters).
*/
export function useStableQueryParams(options: StableQueryParamsOptions) {
- const { typeConfig, allFilters, q, sort, page, pageSize } = options
+ const { typeConfig, allFilters, customFilterRegistry, q, sort, page, pageSize } = options
const stableParams = ref>({})
const buildParams = () => {
@@ -45,7 +47,25 @@ export function useStableQueryParams(options: StableQueryParamsOptions) {
if (filterRef) {
const value = filterRef.value
if (value !== undefined && value !== '' && value !== null) {
- params[filterName as string] = value
+ const paramName = SearchFilterAliases[filterName as string] ?? filterName as string
+ params[paramName] = value
+ }
+ }
+ }
+
+ // 3.5. Apply custom filter values
+ for (const [, entry] of customFilterRegistry) {
+ const value = entry.ref.value
+ if (value !== undefined && value !== '' && value !== null) {
+ const existing = params[entry.apiParam]
+ if (existing !== undefined) {
+ // Concatenate into array for multi-value params (e.g., tag)
+ params[entry.apiParam] = Array.isArray(existing)
+ ? [...existing, value]
+ : [existing, value]
+ }
+ else {
+ params[entry.apiParam] = value
}
}
}
@@ -66,9 +86,19 @@ export function useStableQueryParams(options: StableQueryParamsOptions) {
return params
}
+ // Computed that reads all custom filter values, establishing reactive dependencies
+ // on both the Map mutations (shallowReactive) and each entry's ref.value.
+ const customFilterValues = computed(() => {
+ const snapshot: Record = {}
+ for (const [urlParam, entry] of customFilterRegistry) {
+ snapshot[urlParam] = entry.ref.value
+ }
+ return snapshot
+ })
+
// Watch all dependencies and update only if content changed
watch(
- [q, sort, page, ...Object.values(allFilters)],
+ [q, sort, page, ...Object.values(allFilters), customFilterValues],
() => {
const newParams = buildParams()
// JSON.stringify comparison is safe here because buildParams() builds the object deterministically
diff --git a/datagouv-components/src/main.ts b/datagouv-components/src/main.ts
index 39b418ed7..0b6aa07b6 100644
--- a/datagouv-components/src/main.ts
+++ b/datagouv-components/src/main.ts
@@ -24,7 +24,9 @@ import type { Weight, WellType } from './types/ui'
import type { User, UserReference } from './types/users'
import type { Report, ReportSubject, ReportReason } from './types/reports'
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
-import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
+import { SearchFilterAliases, getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
+import { useSearchFilter } from './composables/useSearchFilter'
+import type { UseSearchFilterOptions } from './composables/useSearchFilter'
import ActivityList from './components/ActivityList/ActivityList.vue'
import UserActivityList from './components/ActivityList/UserActivityList.vue'
@@ -126,6 +128,7 @@ export type {
GlobalSearchConfig,
SearchType,
SortOption,
+ UseSearchFilterOptions,
UseFetchFunction,
AccessType,
AccessAudience,
@@ -219,6 +222,7 @@ export type {
}
export {
+ SearchFilterAliases,
getDefaultDatasetConfig,
getDefaultDataserviceConfig,
getDefaultReuseConfig,
@@ -229,6 +233,7 @@ export {
defaultDataserviceSortOptions,
defaultReuseSortOptions,
defaultOrganizationSortOptions,
+ useSearchFilter,
}
// Vue Plugin
diff --git a/datagouv-components/src/types/search.ts b/datagouv-components/src/types/search.ts
index 30d0f0516..8c33928f5 100644
--- a/datagouv-components/src/types/search.ts
+++ b/datagouv-components/src/types/search.ts
@@ -289,12 +289,17 @@ export type SortOption = {
label: string
}
+// UI filter keys that map to a different API param name
+export const SearchFilterAliases: Record = {
+ organization_facet: 'organization',
+}
+
export type DatasetSearchConfig = {
class: 'datasets'
name?: string
hiddenFilters?: HiddenFilter[]
- basicFilters?: (keyof DatasetSearchFilters)[]
- advancedFilters?: (keyof DatasetSearchFilters)[]
+ basicFilters?: (keyof DatasetSearchFilters | keyof typeof SearchFilterAliases)[]
+ advancedFilters?: (keyof DatasetSearchFilters | keyof typeof SearchFilterAliases)[]
sortOptions?: SortOption[]
}
@@ -302,8 +307,8 @@ export type DataserviceSearchConfig = {
class: 'dataservices'
name?: string
hiddenFilters?: HiddenFilter[]
- basicFilters?: (keyof DataserviceSearchFilters)[]
- advancedFilters?: (keyof DataserviceSearchFilters)[]
+ basicFilters?: (keyof DataserviceSearchFilters | keyof typeof SearchFilterAliases)[]
+ advancedFilters?: (keyof DataserviceSearchFilters | keyof typeof SearchFilterAliases)[]
sortOptions?: SortOption[]
}
@@ -311,8 +316,8 @@ export type ReuseSearchConfig = {
class: 'reuses'
name?: string
hiddenFilters?: HiddenFilter[]
- basicFilters?: (keyof ReuseSearchFilters)[]
- advancedFilters?: (keyof ReuseSearchFilters)[]
+ basicFilters?: (keyof ReuseSearchFilters | keyof typeof SearchFilterAliases)[]
+ advancedFilters?: (keyof ReuseSearchFilters | keyof typeof SearchFilterAliases)[]
sortOptions?: SortOption[]
}
@@ -329,8 +334,8 @@ export type TopicSearchConfig = {
class: 'topics'
name?: string
hiddenFilters?: HiddenFilter[]
- basicFilters?: (keyof TopicSearchFilters)[]
- advancedFilters?: (keyof TopicSearchFilters)[]
+ basicFilters?: (keyof TopicSearchFilters | keyof typeof SearchFilterAliases)[]
+ advancedFilters?: (keyof TopicSearchFilters | keyof typeof SearchFilterAliases)[]
sortOptions?: SortOption[]
}
diff --git a/pages/design/dataset-search.vue b/pages/design/dataset-search.vue
index ab31b0c64..ed7830933 100644
--- a/pages/design/dataset-search.vue
+++ b/pages/design/dataset-search.vue
@@ -13,7 +13,11 @@
-
+
+
+
+
+