@wordpress/views 1.6.0 → 1.6.1-next.v.20260206T143.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.
package/README.md CHANGED
@@ -23,20 +23,7 @@ npm install @wordpress/views --save
23
23
 
24
24
  ### loadView
25
25
 
26
- Async function for loading view state in route loaders with optional URL parameters.
27
-
28
- _Usage_
29
-
30
- ```typescript
31
- // In route loader
32
- const view = await loadView( {
33
- kind: 'taxonomy',
34
- name: 'category',
35
- slug: 'all',
36
- defaultView,
37
- queryParams: { page: search.page, search: search.search },
38
- } );
39
- ```
26
+ Async function for loading view state in route loaders.
40
27
 
41
28
  _Parameters_
42
29
 
@@ -45,6 +32,7 @@ _Parameters_
45
32
  - _config.name_ `ViewConfig`: Specific entity name.
46
33
  - _config.slug_ `ViewConfig`: View identifier.
47
34
  - _config.defaultView_ `ViewConfig`: Default view configuration.
35
+ - _config.activeViewOverrides_ `ViewConfig`: View overrides applied on top but never persisted.
48
36
  - _config.queryParams_ `ViewConfig`: Object with `page` and/or `search` from URL.
49
37
 
50
38
  _Returns_
@@ -58,12 +46,6 @@ Hook for managing DataViews view state with local persistence.
58
46
  _Parameters_
59
47
 
60
48
  - _config_ `ViewConfig`: Configuration object for loading the view.
61
- - _config.kind_ `ViewConfig`: Entity kind (e.g., 'postType', 'taxonomy', 'root').
62
- - _config.name_ `ViewConfig`: Specific entity name.
63
- - _config.slug_ `ViewConfig`: View identifier.
64
- - _config.defaultView_ `ViewConfig`: Default view configuration.
65
- - _config.queryParams_ `ViewConfig`: Object with `page` and/or `search` from URL.
66
- - _config.onChangeQueryParams_ `ViewConfig`: Optional callback to update URL parameters.
67
49
 
68
50
  _Returns_
69
51
 
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/views/src/filter-utils.ts
21
+ var filter_utils_exports = {};
22
+ __export(filter_utils_exports, {
23
+ mergeActiveViewOverrides: () => mergeActiveViewOverrides,
24
+ stripActiveViewOverrides: () => stripActiveViewOverrides
25
+ });
26
+ module.exports = __toCommonJS(filter_utils_exports);
27
+ function mergeActiveViewOverrides(view, activeViewOverrides, defaultView) {
28
+ if (!activeViewOverrides) {
29
+ return view;
30
+ }
31
+ let result = view;
32
+ if (activeViewOverrides.filters && activeViewOverrides.filters.length > 0) {
33
+ const activeFields = new Set(
34
+ activeViewOverrides.filters.map((f) => f.field)
35
+ );
36
+ const preserved = (view.filters ?? []).filter(
37
+ (f) => !activeFields.has(f.field)
38
+ );
39
+ result = {
40
+ ...result,
41
+ filters: [...preserved, ...activeViewOverrides.filters]
42
+ };
43
+ }
44
+ if (activeViewOverrides.sort) {
45
+ const isDefaultSort = defaultView && view.sort?.field === defaultView.sort?.field && view.sort?.direction === defaultView.sort?.direction;
46
+ if (isDefaultSort) {
47
+ result = {
48
+ ...result,
49
+ sort: activeViewOverrides.sort
50
+ };
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+ function stripActiveViewOverrides(view, activeViewOverrides, defaultView) {
56
+ if (!activeViewOverrides) {
57
+ return view;
58
+ }
59
+ let result = view;
60
+ if (activeViewOverrides.filters && activeViewOverrides.filters.length > 0) {
61
+ const activeFields = new Set(
62
+ activeViewOverrides.filters.map((f) => f.field)
63
+ );
64
+ result = {
65
+ ...result,
66
+ filters: (view.filters ?? []).filter(
67
+ (f) => !activeFields.has(f.field)
68
+ )
69
+ };
70
+ }
71
+ if (activeViewOverrides.sort && view.sort?.field === activeViewOverrides.sort.field && view.sort?.direction === activeViewOverrides.sort.direction) {
72
+ result = {
73
+ ...result,
74
+ sort: defaultView?.sort
75
+ };
76
+ }
77
+ return result;
78
+ }
79
+ // Annotate the CommonJS export names for ESM import in node:
80
+ 0 && (module.exports = {
81
+ mergeActiveViewOverrides,
82
+ stripActiveViewOverrides
83
+ });
84
+ //# sourceMappingURL=filter-utils.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/filter-utils.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { View, Filter } from '@wordpress/dataviews';\n\ntype ActiveViewOverrides = {\n\tfilters?: Filter[];\n\tsort?: View[ 'sort' ];\n};\n\n/**\n * Merges activeViewOverrides into a view.\n * Filters: Active filters take precedence; same-field filters are replaced.\n * Sort: Active sort is applied only if current sort matches the default.\n *\n * @param view The view to merge overrides into.\n * @param activeViewOverrides The tab-specific overrides to apply.\n * @param defaultView The default view configuration.\n * @return A new view with merged overrides, or the original view if no overrides.\n */\nexport function mergeActiveViewOverrides(\n\tview: View,\n\tactiveViewOverrides?: ActiveViewOverrides,\n\tdefaultView?: View\n): View {\n\tif ( ! activeViewOverrides ) {\n\t\treturn view;\n\t}\n\n\tlet result = view;\n\n\t// Merge filters\n\tif (\n\t\tactiveViewOverrides.filters &&\n\t\tactiveViewOverrides.filters.length > 0\n\t) {\n\t\tconst activeFields = new Set(\n\t\t\tactiveViewOverrides.filters.map( ( f ) => f.field )\n\t\t);\n\t\tconst preserved = ( view.filters ?? [] ).filter(\n\t\t\t( f: Filter ) => ! activeFields.has( f.field )\n\t\t);\n\t\tresult = {\n\t\t\t...result,\n\t\t\tfilters: [ ...preserved, ...activeViewOverrides.filters ],\n\t\t};\n\t}\n\n\t// Merge sort - only apply if the current sort matches the default\n\tif ( activeViewOverrides.sort ) {\n\t\tconst isDefaultSort =\n\t\t\tdefaultView &&\n\t\t\tview.sort?.field === defaultView.sort?.field &&\n\t\t\tview.sort?.direction === defaultView.sort?.direction;\n\n\t\tif ( isDefaultSort ) {\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tsort: activeViewOverrides.sort,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Strips overrides before persisting.\n * Filters: Removes filters on fields managed by activeViewOverrides.\n * Sort: If sort matches the override, restores the default sort.\n *\n * @param view The view to strip overrides from.\n * @param activeViewOverrides The tab-specific override definitions.\n * @param defaultView The default view configuration.\n * @return A new view with overrides stripped, or the original view if no overrides.\n */\nexport function stripActiveViewOverrides(\n\tview: View,\n\tactiveViewOverrides?: ActiveViewOverrides,\n\tdefaultView?: View\n): View {\n\tif ( ! activeViewOverrides ) {\n\t\treturn view;\n\t}\n\n\tlet result = view;\n\n\t// Strip managed filters\n\tif (\n\t\tactiveViewOverrides.filters &&\n\t\tactiveViewOverrides.filters.length > 0\n\t) {\n\t\tconst activeFields = new Set(\n\t\t\tactiveViewOverrides.filters.map( ( f ) => f.field )\n\t\t);\n\t\tresult = {\n\t\t\t...result,\n\t\t\tfilters: ( view.filters ?? [] ).filter(\n\t\t\t\t( f: Filter ) => ! activeFields.has( f.field )\n\t\t\t),\n\t\t};\n\t}\n\n\t// Strip sort if it matches the override (restore to default)\n\tif (\n\t\tactiveViewOverrides.sort &&\n\t\tview.sort?.field === activeViewOverrides.sort.field &&\n\t\tview.sort?.direction === activeViewOverrides.sort.direction\n\t) {\n\t\tresult = {\n\t\t\t...result,\n\t\t\tsort: defaultView?.sort,\n\t\t};\n\t}\n\n\treturn result;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBO,SAAS,yBACf,MACA,qBACA,aACO;AACP,MAAK,CAAE,qBAAsB;AAC5B,WAAO;AAAA,EACR;AAEA,MAAI,SAAS;AAGb,MACC,oBAAoB,WACpB,oBAAoB,QAAQ,SAAS,GACpC;AACD,UAAM,eAAe,IAAI;AAAA,MACxB,oBAAoB,QAAQ,IAAK,CAAE,MAAO,EAAE,KAAM;AAAA,IACnD;AACA,UAAM,aAAc,KAAK,WAAW,CAAC,GAAI;AAAA,MACxC,CAAE,MAAe,CAAE,aAAa,IAAK,EAAE,KAAM;AAAA,IAC9C;AACA,aAAS;AAAA,MACR,GAAG;AAAA,MACH,SAAS,CAAE,GAAG,WAAW,GAAG,oBAAoB,OAAQ;AAAA,IACzD;AAAA,EACD;AAGA,MAAK,oBAAoB,MAAO;AAC/B,UAAM,gBACL,eACA,KAAK,MAAM,UAAU,YAAY,MAAM,SACvC,KAAK,MAAM,cAAc,YAAY,MAAM;AAE5C,QAAK,eAAgB;AACpB,eAAS;AAAA,QACR,GAAG;AAAA,QACH,MAAM,oBAAoB;AAAA,MAC3B;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAYO,SAAS,yBACf,MACA,qBACA,aACO;AACP,MAAK,CAAE,qBAAsB;AAC5B,WAAO;AAAA,EACR;AAEA,MAAI,SAAS;AAGb,MACC,oBAAoB,WACpB,oBAAoB,QAAQ,SAAS,GACpC;AACD,UAAM,eAAe,IAAI;AAAA,MACxB,oBAAoB,QAAQ,IAAK,CAAE,MAAO,EAAE,KAAM;AAAA,IACnD;AACA,aAAS;AAAA,MACR,GAAG;AAAA,MACH,UAAW,KAAK,WAAW,CAAC,GAAI;AAAA,QAC/B,CAAE,MAAe,CAAE,aAAa,IAAK,EAAE,KAAM;AAAA,MAC9C;AAAA,IACD;AAAA,EACD;AAGA,MACC,oBAAoB,QACpB,KAAK,MAAM,UAAU,oBAAoB,KAAK,SAC9C,KAAK,MAAM,cAAc,oBAAoB,KAAK,WACjD;AACD,aAAS;AAAA,MACR,GAAG;AAAA,MACH,MAAM,aAAa;AAAA,IACpB;AAAA,EACD;AAEA,SAAO;AACR;",
6
+ "names": []
7
+ }
@@ -26,8 +26,9 @@ module.exports = __toCommonJS(load_view_exports);
26
26
  var import_data = require("@wordpress/data");
27
27
  var import_preferences = require("@wordpress/preferences");
28
28
  var import_preference_keys = require("./preference-keys.cjs");
29
+ var import_filter_utils = require("./filter-utils.cjs");
29
30
  async function loadView(config) {
30
- const { kind, name, slug, defaultView, queryParams } = config;
31
+ const { kind, name, slug, defaultView, activeViewOverrides, queryParams } = config;
31
32
  const preferenceKey = (0, import_preference_keys.generatePreferenceKey)(kind, name, slug);
32
33
  const persistedView = (0, import_data.select)(import_preferences.store).get(
33
34
  "core/views",
@@ -36,11 +37,15 @@ async function loadView(config) {
36
37
  const baseView = persistedView ?? defaultView;
37
38
  const page = queryParams?.page ?? 1;
38
39
  const search = queryParams?.search ?? "";
39
- return {
40
- ...baseView,
41
- page,
42
- search
43
- };
40
+ return (0, import_filter_utils.mergeActiveViewOverrides)(
41
+ {
42
+ ...baseView,
43
+ page,
44
+ search
45
+ },
46
+ activeViewOverrides,
47
+ defaultView
48
+ );
44
49
  }
45
50
  // Annotate the CommonJS export names for ESM import in node:
46
51
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/load-view.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { select } from '@wordpress/data';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\nimport type { View } from '@wordpress/dataviews';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport type { ViewConfig } from './types';\n\n/**\n * Async function for loading view state in route loaders with optional URL parameters.\n *\n * @example\n *\n * ```typescript\n * // In route loader\n * const view = await loadView( {\n * \tkind: 'taxonomy',\n * \tname: 'category',\n * \tslug: 'all',\n * \tdefaultView,\n * \tqueryParams: { page: search.page, search: search.search },\n * } );\n * ```\n *\n * @param config Configuration object for loading the view.\n * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').\n * @param config.name Specific entity name.\n * @param config.slug View identifier.\n * @param config.defaultView Default view configuration.\n * @param config.queryParams Object with `page` and/or `search` from URL.\n *\n * @return Promise resolving to the loaded view object.\n */\nexport async function loadView( config: ViewConfig ) {\n\tconst { kind, name, slug, defaultView, queryParams } = config;\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = select( preferencesStore ).get(\n\t\t'core/views',\n\t\tpreferenceKey\n\t) as View | undefined;\n\n\tconst baseView = persistedView ?? defaultView;\n\tconst page = queryParams?.page ?? 1;\n\tconst search = queryParams?.search ?? '';\n\n\treturn {\n\t\t...baseView,\n\t\tpage,\n\t\tsearch,\n\t};\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAuB;AAEvB,yBAA0C;AAM1C,6BAAsC;AA4BtC,eAAsB,SAAU,QAAqB;AACpD,QAAM,EAAE,MAAM,MAAM,MAAM,aAAa,YAAY,IAAI;AACvD,QAAM,oBAAgB,8CAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,oBAAkC,oBAAQ,mBAAAA,KAAiB,EAAE;AAAA,IAClE;AAAA,IACA;AAAA,EACD;AAEA,QAAM,WAAW,iBAAiB;AAClC,QAAM,OAAO,aAAa,QAAQ;AAClC,QAAM,SAAS,aAAa,UAAU;AAEtC,SAAO;AAAA,IACN,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { select } from '@wordpress/data';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\nimport type { View } from '@wordpress/dataviews';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport { mergeActiveViewOverrides } from './filter-utils';\nimport type { ViewConfig } from './types';\n\n/**\n * Async function for loading view state in route loaders.\n *\n * @param config Configuration object for loading the view.\n * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').\n * @param config.name Specific entity name.\n * @param config.slug View identifier.\n * @param config.defaultView Default view configuration.\n * @param config.activeViewOverrides View overrides applied on top but never persisted.\n * @param config.queryParams Object with `page` and/or `search` from URL.\n * @return Promise resolving to the loaded view object.\n */\nexport async function loadView( config: ViewConfig ) {\n\tconst { kind, name, slug, defaultView, activeViewOverrides, queryParams } =\n\t\tconfig;\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = select( preferencesStore ).get(\n\t\t'core/views',\n\t\tpreferenceKey\n\t) as View | undefined;\n\n\tconst baseView = persistedView ?? defaultView;\n\tconst page = queryParams?.page ?? 1;\n\tconst search = queryParams?.search ?? '';\n\n\treturn mergeActiveViewOverrides(\n\t\t{\n\t\t\t...baseView,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t},\n\t\tactiveViewOverrides,\n\t\tdefaultView\n\t);\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAuB;AAEvB,yBAA0C;AAM1C,6BAAsC;AACtC,0BAAyC;AAezC,eAAsB,SAAU,QAAqB;AACpD,QAAM,EAAE,MAAM,MAAM,MAAM,aAAa,qBAAqB,YAAY,IACvE;AACD,QAAM,oBAAgB,8CAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,oBAAkC,oBAAQ,mBAAAA,KAAiB,EAAE;AAAA,IAClE;AAAA,IACA;AAAA,EACD;AAEA,QAAM,WAAW,iBAAiB;AAClC,QAAM,OAAO,aAAa,QAAQ;AAClC,QAAM,SAAS,aAAa,UAAU;AAEtC,aAAO;AAAA,IACN;AAAA,MACC,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
6
6
  "names": ["preferencesStore"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/types.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { View } from '@wordpress/dataviews';\n\nexport interface ViewConfig {\n\t/**\n\t * Entity kind (e.g. postType, root).\n\t * Similar to core-data entity kinds.\n\t */\n\tkind: string;\n\n\t/**\n\t * Entity name (e.g. post, page, user, site).\n\t * Similar to core-data entity names.\n\t */\n\tname: string;\n\n\t/**\n\t * Identifier for the current view (all, published, mine, etc.)\n\t */\n\tslug: string;\n\n\t/**\n\t * Default view configuration\n\t */\n\tdefaultView: View;\n\n\t/**\n\t * Optional query parameters from URL (page, search)\n\t */\n\tqueryParams?: {\n\t\tpage?: number;\n\t\tsearch?: string;\n\t};\n\n\t/**\n\t * Callback for when URL query parameters should change\n\t */\n\tonChangeQueryParams?: ( params: {\n\t\tpage?: number;\n\t\tsearch?: string;\n\t} ) => void;\n}\n"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { View, Filter } from '@wordpress/dataviews';\n\nexport interface ViewConfig {\n\t/**\n\t * Entity kind (e.g. postType, root).\n\t * Similar to core-data entity kinds.\n\t */\n\tkind: string;\n\n\t/**\n\t * Entity name (e.g. post, page, user, site).\n\t * Similar to core-data entity names.\n\t */\n\tname: string;\n\n\t/**\n\t * Identifier for the current view (all, published, mine, etc.)\n\t */\n\tslug: string;\n\n\t/**\n\t * Default view configuration\n\t */\n\tdefaultView: View;\n\n\t/**\n\t * View overrides applied on top of the persisted view but never persisted.\n\t * These represent tab-specific configuration (filters, sort) that should\n\t * override the persisted view settings.\n\t */\n\tactiveViewOverrides?: {\n\t\tfilters?: Filter[];\n\t\tsort?: View[ 'sort' ];\n\t};\n\n\t/**\n\t * Optional query parameters from URL (page, search)\n\t */\n\tqueryParams?: {\n\t\tpage?: number;\n\t\tsearch?: string;\n\t};\n\n\t/**\n\t * Callback for when URL query parameters should change\n\t */\n\tonChangeQueryParams?: ( params: {\n\t\tpage?: number;\n\t\tsearch?: string;\n\t} ) => void;\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -24,10 +24,11 @@ __export(use_view_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(use_view_exports);
26
26
  var import_dequal = require("dequal");
27
- var import_preference_keys = require("./preference-keys.cjs");
28
27
  var import_element = require("@wordpress/element");
29
28
  var import_data = require("@wordpress/data");
30
29
  var import_preferences = require("@wordpress/preferences");
30
+ var import_preference_keys = require("./preference-keys.cjs");
31
+ var import_filter_utils = require("./filter-utils.cjs");
31
32
  function omit(obj, keys) {
32
33
  const result = { ...obj };
33
34
  for (const key of keys) {
@@ -36,7 +37,15 @@ function omit(obj, keys) {
36
37
  return result;
37
38
  }
38
39
  function useView(config) {
39
- const { kind, name, slug, defaultView, queryParams, onChangeQueryParams } = config;
40
+ const {
41
+ kind,
42
+ name,
43
+ slug,
44
+ defaultView,
45
+ activeViewOverrides,
46
+ queryParams,
47
+ onChangeQueryParams
48
+ } = config;
40
49
  const preferenceKey = (0, import_preference_keys.generatePreferenceKey)(kind, name, slug);
41
50
  const persistedView = (0, import_data.useSelect)(
42
51
  (select) => {
@@ -52,12 +61,16 @@ function useView(config) {
52
61
  const page = Number(queryParams?.page ?? baseView.page ?? 1);
53
62
  const search = queryParams?.search ?? baseView.search ?? "";
54
63
  const view = (0, import_element.useMemo)(() => {
55
- return {
56
- ...baseView,
57
- page,
58
- search
59
- };
60
- }, [baseView, page, search]);
64
+ return (0, import_filter_utils.mergeActiveViewOverrides)(
65
+ {
66
+ ...baseView,
67
+ page,
68
+ search
69
+ },
70
+ activeViewOverrides,
71
+ defaultView
72
+ );
73
+ }, [baseView, page, search, activeViewOverrides, defaultView]);
61
74
  const isModified = !!persistedView;
62
75
  const updateView = (0, import_element.useCallback)(
63
76
  (newView) => {
@@ -65,12 +78,26 @@ function useView(config) {
65
78
  page: newView?.page,
66
79
  search: newView?.search
67
80
  };
68
- const preferenceView = omit(newView, ["page", "search"]);
81
+ const preferenceView = (0, import_filter_utils.stripActiveViewOverrides)(
82
+ omit(newView, ["page", "search"]),
83
+ activeViewOverrides,
84
+ defaultView
85
+ );
69
86
  if (onChangeQueryParams && !(0, import_dequal.dequal)(urlParams, { page, search })) {
70
87
  onChangeQueryParams(urlParams);
71
88
  }
72
- if (!(0, import_dequal.dequal)(baseView, preferenceView)) {
73
- if ((0, import_dequal.dequal)(preferenceView, defaultView)) {
89
+ const comparableBaseView = (0, import_filter_utils.stripActiveViewOverrides)(
90
+ baseView,
91
+ activeViewOverrides,
92
+ defaultView
93
+ );
94
+ const comparableDefaultView = (0, import_filter_utils.stripActiveViewOverrides)(
95
+ defaultView,
96
+ activeViewOverrides,
97
+ defaultView
98
+ );
99
+ if (!(0, import_dequal.dequal)(comparableBaseView, preferenceView)) {
100
+ if ((0, import_dequal.dequal)(preferenceView, comparableDefaultView)) {
74
101
  set("core/views", preferenceKey, void 0);
75
102
  } else {
76
103
  set("core/views", preferenceKey, preferenceView);
@@ -83,6 +110,7 @@ function useView(config) {
83
110
  search,
84
111
  baseView,
85
112
  defaultView,
113
+ activeViewOverrides,
86
114
  set,
87
115
  preferenceKey
88
116
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/use-view.ts"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport { dequal } from 'dequal';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport type { ViewConfig } from './types';\n\n/**\n * WordPress dependencies\n */\nimport { useCallback, useMemo } from '@wordpress/element';\nimport { useDispatch, useSelect } from '@wordpress/data';\nimport type { View } from '@wordpress/dataviews';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\n\ninterface UseViewReturn {\n\tview: View;\n\tisModified: boolean;\n\tupdateView: ( newView: View ) => void;\n\tresetToDefault: () => void;\n}\n\nfunction omit< T extends object, K extends keyof T >(\n\tobj: T,\n\tkeys: K[]\n): Omit< T, K > {\n\tconst result = { ...obj };\n\tfor ( const key of keys ) {\n\t\tdelete result[ key ];\n\t}\n\treturn result;\n}\n\n/**\n * Hook for managing DataViews view state with local persistence.\n *\n * @param config Configuration object for loading the view.\n * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').\n * @param config.name Specific entity name.\n * @param config.slug View identifier.\n * @param config.defaultView Default view configuration.\n * @param config.queryParams Object with `page` and/or `search` from URL.\n * @param config.onChangeQueryParams Optional callback to update URL parameters.\n *\n * @return Object with current view, modification state, and update functions.\n */\nexport function useView( config: ViewConfig ): UseViewReturn {\n\tconst { kind, name, slug, defaultView, queryParams, onChangeQueryParams } =\n\t\tconfig;\n\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = useSelect(\n\t\t( select ) => {\n\t\t\treturn select( preferencesStore ).get(\n\t\t\t\t'core/views',\n\t\t\t\tpreferenceKey\n\t\t\t) as View | undefined;\n\t\t},\n\t\t[ preferenceKey ]\n\t);\n\tconst { set } = useDispatch( preferencesStore );\n\n\tconst baseView: View = persistedView ?? defaultView;\n\tconst page = Number( queryParams?.page ?? baseView.page ?? 1 );\n\tconst search = queryParams?.search ?? baseView.search ?? '';\n\n\t// Merge URL query parameters (page, search) into the view\n\tconst view: View = useMemo( () => {\n\t\treturn {\n\t\t\t...baseView,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t};\n\t}, [ baseView, page, search ] );\n\n\tconst isModified = !! persistedView;\n\n\tconst updateView = useCallback(\n\t\t( newView: View ) => {\n\t\t\t// Extract URL params (page, search) from the new view\n\t\t\tconst urlParams: { page?: number; search?: string } = {\n\t\t\t\tpage: newView?.page,\n\t\t\t\tsearch: newView?.search,\n\t\t\t};\n\t\t\tconst preferenceView = omit( newView, [ 'page', 'search' ] );\n\n\t\t\t// If we have URL handling enabled, separate URL state from preference state\n\t\t\tif (\n\t\t\t\tonChangeQueryParams &&\n\t\t\t\t! dequal( urlParams, { page, search } )\n\t\t\t) {\n\t\t\t\tonChangeQueryParams( urlParams );\n\t\t\t}\n\n\t\t\t// Only persist non-URL preferences if different from baseView\n\t\t\tif ( ! dequal( baseView, preferenceView ) ) {\n\t\t\t\tif ( dequal( preferenceView, defaultView ) ) {\n\t\t\t\t\tset( 'core/views', preferenceKey, undefined );\n\t\t\t\t} else {\n\t\t\t\t\tset( 'core/views', preferenceKey, preferenceView );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tonChangeQueryParams,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t\tbaseView,\n\t\t\tdefaultView,\n\t\t\tset,\n\t\t\tpreferenceKey,\n\t\t]\n\t);\n\n\tconst resetToDefault = useCallback( () => {\n\t\tset( 'core/views', preferenceKey, undefined );\n\t}, [ preferenceKey, set ] );\n\n\treturn {\n\t\tview,\n\t\tisModified,\n\t\tupdateView,\n\t\tresetToDefault,\n\t};\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAuB;AAKvB,6BAAsC;AAMtC,qBAAqC;AACrC,kBAAuC;AAGvC,yBAA0C;AAS1C,SAAS,KACR,KACA,MACe;AACf,QAAM,SAAS,EAAE,GAAG,IAAI;AACxB,aAAY,OAAO,MAAO;AACzB,WAAO,OAAQ,GAAI;AAAA,EACpB;AACA,SAAO;AACR;AAeO,SAAS,QAAS,QAAoC;AAC5D,QAAM,EAAE,MAAM,MAAM,MAAM,aAAa,aAAa,oBAAoB,IACvE;AAED,QAAM,oBAAgB,8CAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,oBAAkC;AAAA,IACvC,CAAE,WAAY;AACb,aAAO,OAAQ,mBAAAA,KAAiB,EAAE;AAAA,QACjC;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,IACA,CAAE,aAAc;AAAA,EACjB;AACA,QAAM,EAAE,IAAI,QAAI,yBAAa,mBAAAA,KAAiB;AAE9C,QAAM,WAAiB,iBAAiB;AACxC,QAAM,OAAO,OAAQ,aAAa,QAAQ,SAAS,QAAQ,CAAE;AAC7D,QAAM,SAAS,aAAa,UAAU,SAAS,UAAU;AAGzD,QAAM,WAAa,wBAAS,MAAM;AACjC,WAAO;AAAA,MACN,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACD;AAAA,EACD,GAAG,CAAE,UAAU,MAAM,MAAO,CAAE;AAE9B,QAAM,aAAa,CAAC,CAAE;AAEtB,QAAM,iBAAa;AAAA,IAClB,CAAE,YAAmB;AAEpB,YAAM,YAAgD;AAAA,QACrD,MAAM,SAAS;AAAA,QACf,QAAQ,SAAS;AAAA,MAClB;AACA,YAAM,iBAAiB,KAAM,SAAS,CAAE,QAAQ,QAAS,CAAE;AAG3D,UACC,uBACA,KAAE,sBAAQ,WAAW,EAAE,MAAM,OAAO,CAAE,GACrC;AACD,4BAAqB,SAAU;AAAA,MAChC;AAGA,UAAK,KAAE,sBAAQ,UAAU,cAAe,GAAI;AAC3C,gBAAK,sBAAQ,gBAAgB,WAAY,GAAI;AAC5C,cAAK,cAAc,eAAe,MAAU;AAAA,QAC7C,OAAO;AACN,cAAK,cAAc,eAAe,cAAe;AAAA,QAClD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAEA,QAAM,qBAAiB,4BAAa,MAAM;AACzC,QAAK,cAAc,eAAe,MAAU;AAAA,EAC7C,GAAG,CAAE,eAAe,GAAI,CAAE;AAE1B,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport { dequal } from 'dequal';\n\n/**\n * WordPress dependencies\n */\nimport { useCallback, useMemo } from '@wordpress/element';\nimport { useDispatch, useSelect } from '@wordpress/data';\nimport type { View } from '@wordpress/dataviews';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport {\n\tmergeActiveViewOverrides,\n\tstripActiveViewOverrides,\n} from './filter-utils';\nimport type { ViewConfig } from './types';\n\ninterface UseViewReturn {\n\tview: View;\n\tisModified: boolean;\n\tupdateView: ( newView: View ) => void;\n\tresetToDefault: () => void;\n}\n\nfunction omit< T extends object, K extends keyof T >(\n\tobj: T,\n\tkeys: K[]\n): Omit< T, K > {\n\tconst result = { ...obj };\n\tfor ( const key of keys ) {\n\t\tdelete result[ key ];\n\t}\n\treturn result;\n}\n\n/**\n * Hook for managing DataViews view state with local persistence.\n *\n * @param config Configuration object for loading the view.\n *\n * @return Object with current view, modification state, and update functions.\n */\nexport function useView( config: ViewConfig ): UseViewReturn {\n\tconst {\n\t\tkind,\n\t\tname,\n\t\tslug,\n\t\tdefaultView,\n\t\tactiveViewOverrides,\n\t\tqueryParams,\n\t\tonChangeQueryParams,\n\t} = config;\n\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = useSelect(\n\t\t( select ) => {\n\t\t\treturn select( preferencesStore ).get(\n\t\t\t\t'core/views',\n\t\t\t\tpreferenceKey\n\t\t\t) as View | undefined;\n\t\t},\n\t\t[ preferenceKey ]\n\t);\n\tconst { set } = useDispatch( preferencesStore );\n\n\tconst baseView: View = persistedView ?? defaultView;\n\tconst page = Number( queryParams?.page ?? baseView.page ?? 1 );\n\tconst search = queryParams?.search ?? baseView.search ?? '';\n\n\t// Merge URL query parameters (page, search) and activeViewOverrides into the view\n\tconst view: View = useMemo( () => {\n\t\treturn mergeActiveViewOverrides(\n\t\t\t{\n\t\t\t\t...baseView,\n\t\t\t\tpage,\n\t\t\t\tsearch,\n\t\t\t},\n\t\t\tactiveViewOverrides,\n\t\t\tdefaultView\n\t\t);\n\t}, [ baseView, page, search, activeViewOverrides, defaultView ] );\n\n\tconst isModified = !! persistedView;\n\n\tconst updateView = useCallback(\n\t\t( newView: View ) => {\n\t\t\t// Extract URL params (page, search) from the new view\n\t\t\tconst urlParams: { page?: number; search?: string } = {\n\t\t\t\tpage: newView?.page,\n\t\t\t\tsearch: newView?.search,\n\t\t\t};\n\t\t\t// Strip activeViewOverrides and URL params before persisting\n\t\t\t// Cast is safe: omitting page/search doesn't change the discriminant (type field)\n\t\t\tconst preferenceView = stripActiveViewOverrides(\n\t\t\t\tomit( newView, [ 'page', 'search' ] ) as View,\n\t\t\t\tactiveViewOverrides,\n\t\t\t\tdefaultView\n\t\t\t);\n\n\t\t\t// If we have URL handling enabled, separate URL state from preference state\n\t\t\tif (\n\t\t\t\tonChangeQueryParams &&\n\t\t\t\t! dequal( urlParams, { page, search } )\n\t\t\t) {\n\t\t\t\tonChangeQueryParams( urlParams );\n\t\t\t}\n\n\t\t\t// Compare with baseView and defaultView after stripping activeViewOverrides\n\t\t\tconst comparableBaseView = stripActiveViewOverrides(\n\t\t\t\tbaseView,\n\t\t\t\tactiveViewOverrides,\n\t\t\t\tdefaultView\n\t\t\t);\n\t\t\tconst comparableDefaultView = stripActiveViewOverrides(\n\t\t\t\tdefaultView,\n\t\t\t\tactiveViewOverrides,\n\t\t\t\tdefaultView\n\t\t\t);\n\n\t\t\t// Only persist non-URL preferences if different from baseView\n\t\t\tif ( ! dequal( comparableBaseView, preferenceView ) ) {\n\t\t\t\tif ( dequal( preferenceView, comparableDefaultView ) ) {\n\t\t\t\t\tset( 'core/views', preferenceKey, undefined );\n\t\t\t\t} else {\n\t\t\t\t\tset( 'core/views', preferenceKey, preferenceView );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tonChangeQueryParams,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t\tbaseView,\n\t\t\tdefaultView,\n\t\t\tactiveViewOverrides,\n\t\t\tset,\n\t\t\tpreferenceKey,\n\t\t]\n\t);\n\n\tconst resetToDefault = useCallback( () => {\n\t\tset( 'core/views', preferenceKey, undefined );\n\t}, [ preferenceKey, set ] );\n\n\treturn {\n\t\tview,\n\t\tisModified,\n\t\tupdateView,\n\t\tresetToDefault,\n\t};\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAuB;AAKvB,qBAAqC;AACrC,kBAAuC;AAGvC,yBAA0C;AAK1C,6BAAsC;AACtC,0BAGO;AAUP,SAAS,KACR,KACA,MACe;AACf,QAAM,SAAS,EAAE,GAAG,IAAI;AACxB,aAAY,OAAO,MAAO;AACzB,WAAO,OAAQ,GAAI;AAAA,EACpB;AACA,SAAO;AACR;AASO,SAAS,QAAS,QAAoC;AAC5D,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,QAAM,oBAAgB,8CAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,oBAAkC;AAAA,IACvC,CAAE,WAAY;AACb,aAAO,OAAQ,mBAAAA,KAAiB,EAAE;AAAA,QACjC;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,IACA,CAAE,aAAc;AAAA,EACjB;AACA,QAAM,EAAE,IAAI,QAAI,yBAAa,mBAAAA,KAAiB;AAE9C,QAAM,WAAiB,iBAAiB;AACxC,QAAM,OAAO,OAAQ,aAAa,QAAQ,SAAS,QAAQ,CAAE;AAC7D,QAAM,SAAS,aAAa,UAAU,SAAS,UAAU;AAGzD,QAAM,WAAa,wBAAS,MAAM;AACjC,eAAO;AAAA,MACN;AAAA,QACC,GAAG;AAAA,QACH;AAAA,QACA;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD,GAAG,CAAE,UAAU,MAAM,QAAQ,qBAAqB,WAAY,CAAE;AAEhE,QAAM,aAAa,CAAC,CAAE;AAEtB,QAAM,iBAAa;AAAA,IAClB,CAAE,YAAmB;AAEpB,YAAM,YAAgD;AAAA,QACrD,MAAM,SAAS;AAAA,QACf,QAAQ,SAAS;AAAA,MAClB;AAGA,YAAM,qBAAiB;AAAA,QACtB,KAAM,SAAS,CAAE,QAAQ,QAAS,CAAE;AAAA,QACpC;AAAA,QACA;AAAA,MACD;AAGA,UACC,uBACA,KAAE,sBAAQ,WAAW,EAAE,MAAM,OAAO,CAAE,GACrC;AACD,4BAAqB,SAAU;AAAA,MAChC;AAGA,YAAM,yBAAqB;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,MACD;AACA,YAAM,4BAAwB;AAAA,QAC7B;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAGA,UAAK,KAAE,sBAAQ,oBAAoB,cAAe,GAAI;AACrD,gBAAK,sBAAQ,gBAAgB,qBAAsB,GAAI;AACtD,cAAK,cAAc,eAAe,MAAU;AAAA,QAC7C,OAAO;AACN,cAAK,cAAc,eAAe,cAAe;AAAA,QAClD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAEA,QAAM,qBAAiB,4BAAa,MAAM;AACzC,QAAK,cAAc,eAAe,MAAU;AAAA,EAC7C,GAAG,CAAE,eAAe,GAAI,CAAE;AAE1B,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
6
6
  "names": ["preferencesStore"]
7
7
  }
@@ -0,0 +1,58 @@
1
+ // packages/views/src/filter-utils.ts
2
+ function mergeActiveViewOverrides(view, activeViewOverrides, defaultView) {
3
+ if (!activeViewOverrides) {
4
+ return view;
5
+ }
6
+ let result = view;
7
+ if (activeViewOverrides.filters && activeViewOverrides.filters.length > 0) {
8
+ const activeFields = new Set(
9
+ activeViewOverrides.filters.map((f) => f.field)
10
+ );
11
+ const preserved = (view.filters ?? []).filter(
12
+ (f) => !activeFields.has(f.field)
13
+ );
14
+ result = {
15
+ ...result,
16
+ filters: [...preserved, ...activeViewOverrides.filters]
17
+ };
18
+ }
19
+ if (activeViewOverrides.sort) {
20
+ const isDefaultSort = defaultView && view.sort?.field === defaultView.sort?.field && view.sort?.direction === defaultView.sort?.direction;
21
+ if (isDefaultSort) {
22
+ result = {
23
+ ...result,
24
+ sort: activeViewOverrides.sort
25
+ };
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+ function stripActiveViewOverrides(view, activeViewOverrides, defaultView) {
31
+ if (!activeViewOverrides) {
32
+ return view;
33
+ }
34
+ let result = view;
35
+ if (activeViewOverrides.filters && activeViewOverrides.filters.length > 0) {
36
+ const activeFields = new Set(
37
+ activeViewOverrides.filters.map((f) => f.field)
38
+ );
39
+ result = {
40
+ ...result,
41
+ filters: (view.filters ?? []).filter(
42
+ (f) => !activeFields.has(f.field)
43
+ )
44
+ };
45
+ }
46
+ if (activeViewOverrides.sort && view.sort?.field === activeViewOverrides.sort.field && view.sort?.direction === activeViewOverrides.sort.direction) {
47
+ result = {
48
+ ...result,
49
+ sort: defaultView?.sort
50
+ };
51
+ }
52
+ return result;
53
+ }
54
+ export {
55
+ mergeActiveViewOverrides,
56
+ stripActiveViewOverrides
57
+ };
58
+ //# sourceMappingURL=filter-utils.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/filter-utils.ts"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { View, Filter } from '@wordpress/dataviews';\n\ntype ActiveViewOverrides = {\n\tfilters?: Filter[];\n\tsort?: View[ 'sort' ];\n};\n\n/**\n * Merges activeViewOverrides into a view.\n * Filters: Active filters take precedence; same-field filters are replaced.\n * Sort: Active sort is applied only if current sort matches the default.\n *\n * @param view The view to merge overrides into.\n * @param activeViewOverrides The tab-specific overrides to apply.\n * @param defaultView The default view configuration.\n * @return A new view with merged overrides, or the original view if no overrides.\n */\nexport function mergeActiveViewOverrides(\n\tview: View,\n\tactiveViewOverrides?: ActiveViewOverrides,\n\tdefaultView?: View\n): View {\n\tif ( ! activeViewOverrides ) {\n\t\treturn view;\n\t}\n\n\tlet result = view;\n\n\t// Merge filters\n\tif (\n\t\tactiveViewOverrides.filters &&\n\t\tactiveViewOverrides.filters.length > 0\n\t) {\n\t\tconst activeFields = new Set(\n\t\t\tactiveViewOverrides.filters.map( ( f ) => f.field )\n\t\t);\n\t\tconst preserved = ( view.filters ?? [] ).filter(\n\t\t\t( f: Filter ) => ! activeFields.has( f.field )\n\t\t);\n\t\tresult = {\n\t\t\t...result,\n\t\t\tfilters: [ ...preserved, ...activeViewOverrides.filters ],\n\t\t};\n\t}\n\n\t// Merge sort - only apply if the current sort matches the default\n\tif ( activeViewOverrides.sort ) {\n\t\tconst isDefaultSort =\n\t\t\tdefaultView &&\n\t\t\tview.sort?.field === defaultView.sort?.field &&\n\t\t\tview.sort?.direction === defaultView.sort?.direction;\n\n\t\tif ( isDefaultSort ) {\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tsort: activeViewOverrides.sort,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Strips overrides before persisting.\n * Filters: Removes filters on fields managed by activeViewOverrides.\n * Sort: If sort matches the override, restores the default sort.\n *\n * @param view The view to strip overrides from.\n * @param activeViewOverrides The tab-specific override definitions.\n * @param defaultView The default view configuration.\n * @return A new view with overrides stripped, or the original view if no overrides.\n */\nexport function stripActiveViewOverrides(\n\tview: View,\n\tactiveViewOverrides?: ActiveViewOverrides,\n\tdefaultView?: View\n): View {\n\tif ( ! activeViewOverrides ) {\n\t\treturn view;\n\t}\n\n\tlet result = view;\n\n\t// Strip managed filters\n\tif (\n\t\tactiveViewOverrides.filters &&\n\t\tactiveViewOverrides.filters.length > 0\n\t) {\n\t\tconst activeFields = new Set(\n\t\t\tactiveViewOverrides.filters.map( ( f ) => f.field )\n\t\t);\n\t\tresult = {\n\t\t\t...result,\n\t\t\tfilters: ( view.filters ?? [] ).filter(\n\t\t\t\t( f: Filter ) => ! activeFields.has( f.field )\n\t\t\t),\n\t\t};\n\t}\n\n\t// Strip sort if it matches the override (restore to default)\n\tif (\n\t\tactiveViewOverrides.sort &&\n\t\tview.sort?.field === activeViewOverrides.sort.field &&\n\t\tview.sort?.direction === activeViewOverrides.sort.direction\n\t) {\n\t\tresult = {\n\t\t\t...result,\n\t\t\tsort: defaultView?.sort,\n\t\t};\n\t}\n\n\treturn result;\n}\n"],
5
+ "mappings": ";AAoBO,SAAS,yBACf,MACA,qBACA,aACO;AACP,MAAK,CAAE,qBAAsB;AAC5B,WAAO;AAAA,EACR;AAEA,MAAI,SAAS;AAGb,MACC,oBAAoB,WACpB,oBAAoB,QAAQ,SAAS,GACpC;AACD,UAAM,eAAe,IAAI;AAAA,MACxB,oBAAoB,QAAQ,IAAK,CAAE,MAAO,EAAE,KAAM;AAAA,IACnD;AACA,UAAM,aAAc,KAAK,WAAW,CAAC,GAAI;AAAA,MACxC,CAAE,MAAe,CAAE,aAAa,IAAK,EAAE,KAAM;AAAA,IAC9C;AACA,aAAS;AAAA,MACR,GAAG;AAAA,MACH,SAAS,CAAE,GAAG,WAAW,GAAG,oBAAoB,OAAQ;AAAA,IACzD;AAAA,EACD;AAGA,MAAK,oBAAoB,MAAO;AAC/B,UAAM,gBACL,eACA,KAAK,MAAM,UAAU,YAAY,MAAM,SACvC,KAAK,MAAM,cAAc,YAAY,MAAM;AAE5C,QAAK,eAAgB;AACpB,eAAS;AAAA,QACR,GAAG;AAAA,QACH,MAAM,oBAAoB;AAAA,MAC3B;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAYO,SAAS,yBACf,MACA,qBACA,aACO;AACP,MAAK,CAAE,qBAAsB;AAC5B,WAAO;AAAA,EACR;AAEA,MAAI,SAAS;AAGb,MACC,oBAAoB,WACpB,oBAAoB,QAAQ,SAAS,GACpC;AACD,UAAM,eAAe,IAAI;AAAA,MACxB,oBAAoB,QAAQ,IAAK,CAAE,MAAO,EAAE,KAAM;AAAA,IACnD;AACA,aAAS;AAAA,MACR,GAAG;AAAA,MACH,UAAW,KAAK,WAAW,CAAC,GAAI;AAAA,QAC/B,CAAE,MAAe,CAAE,aAAa,IAAK,EAAE,KAAM;AAAA,MAC9C;AAAA,IACD;AAAA,EACD;AAGA,MACC,oBAAoB,QACpB,KAAK,MAAM,UAAU,oBAAoB,KAAK,SAC9C,KAAK,MAAM,cAAc,oBAAoB,KAAK,WACjD;AACD,aAAS;AAAA,MACR,GAAG;AAAA,MACH,MAAM,aAAa;AAAA,IACpB;AAAA,EACD;AAEA,SAAO;AACR;",
6
+ "names": []
7
+ }
@@ -2,8 +2,9 @@
2
2
  import { select } from "@wordpress/data";
3
3
  import { store as preferencesStore } from "@wordpress/preferences";
4
4
  import { generatePreferenceKey } from "./preference-keys.mjs";
5
+ import { mergeActiveViewOverrides } from "./filter-utils.mjs";
5
6
  async function loadView(config) {
6
- const { kind, name, slug, defaultView, queryParams } = config;
7
+ const { kind, name, slug, defaultView, activeViewOverrides, queryParams } = config;
7
8
  const preferenceKey = generatePreferenceKey(kind, name, slug);
8
9
  const persistedView = select(preferencesStore).get(
9
10
  "core/views",
@@ -12,11 +13,15 @@ async function loadView(config) {
12
13
  const baseView = persistedView ?? defaultView;
13
14
  const page = queryParams?.page ?? 1;
14
15
  const search = queryParams?.search ?? "";
15
- return {
16
- ...baseView,
17
- page,
18
- search
19
- };
16
+ return mergeActiveViewOverrides(
17
+ {
18
+ ...baseView,
19
+ page,
20
+ search
21
+ },
22
+ activeViewOverrides,
23
+ defaultView
24
+ );
20
25
  }
21
26
  export {
22
27
  loadView
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/load-view.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { select } from '@wordpress/data';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\nimport type { View } from '@wordpress/dataviews';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport type { ViewConfig } from './types';\n\n/**\n * Async function for loading view state in route loaders with optional URL parameters.\n *\n * @example\n *\n * ```typescript\n * // In route loader\n * const view = await loadView( {\n * \tkind: 'taxonomy',\n * \tname: 'category',\n * \tslug: 'all',\n * \tdefaultView,\n * \tqueryParams: { page: search.page, search: search.search },\n * } );\n * ```\n *\n * @param config Configuration object for loading the view.\n * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').\n * @param config.name Specific entity name.\n * @param config.slug View identifier.\n * @param config.defaultView Default view configuration.\n * @param config.queryParams Object with `page` and/or `search` from URL.\n *\n * @return Promise resolving to the loaded view object.\n */\nexport async function loadView( config: ViewConfig ) {\n\tconst { kind, name, slug, defaultView, queryParams } = config;\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = select( preferencesStore ).get(\n\t\t'core/views',\n\t\tpreferenceKey\n\t) as View | undefined;\n\n\tconst baseView = persistedView ?? defaultView;\n\tconst page = queryParams?.page ?? 1;\n\tconst search = queryParams?.search ?? '';\n\n\treturn {\n\t\t...baseView,\n\t\tpage,\n\t\tsearch,\n\t};\n}\n"],
5
- "mappings": ";AAGA,SAAS,cAAc;AAEvB,SAAS,SAAS,wBAAwB;AAM1C,SAAS,6BAA6B;AA4BtC,eAAsB,SAAU,QAAqB;AACpD,QAAM,EAAE,MAAM,MAAM,MAAM,aAAa,YAAY,IAAI;AACvD,QAAM,gBAAgB,sBAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,gBAAkC,OAAQ,gBAAiB,EAAE;AAAA,IAClE;AAAA,IACA;AAAA,EACD;AAEA,QAAM,WAAW,iBAAiB;AAClC,QAAM,OAAO,aAAa,QAAQ;AAClC,QAAM,SAAS,aAAa,UAAU;AAEtC,SAAO;AAAA,IACN,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { select } from '@wordpress/data';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\nimport type { View } from '@wordpress/dataviews';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport { mergeActiveViewOverrides } from './filter-utils';\nimport type { ViewConfig } from './types';\n\n/**\n * Async function for loading view state in route loaders.\n *\n * @param config Configuration object for loading the view.\n * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').\n * @param config.name Specific entity name.\n * @param config.slug View identifier.\n * @param config.defaultView Default view configuration.\n * @param config.activeViewOverrides View overrides applied on top but never persisted.\n * @param config.queryParams Object with `page` and/or `search` from URL.\n * @return Promise resolving to the loaded view object.\n */\nexport async function loadView( config: ViewConfig ) {\n\tconst { kind, name, slug, defaultView, activeViewOverrides, queryParams } =\n\t\tconfig;\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = select( preferencesStore ).get(\n\t\t'core/views',\n\t\tpreferenceKey\n\t) as View | undefined;\n\n\tconst baseView = persistedView ?? defaultView;\n\tconst page = queryParams?.page ?? 1;\n\tconst search = queryParams?.search ?? '';\n\n\treturn mergeActiveViewOverrides(\n\t\t{\n\t\t\t...baseView,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t},\n\t\tactiveViewOverrides,\n\t\tdefaultView\n\t);\n}\n"],
5
+ "mappings": ";AAGA,SAAS,cAAc;AAEvB,SAAS,SAAS,wBAAwB;AAM1C,SAAS,6BAA6B;AACtC,SAAS,gCAAgC;AAezC,eAAsB,SAAU,QAAqB;AACpD,QAAM,EAAE,MAAM,MAAM,MAAM,aAAa,qBAAqB,YAAY,IACvE;AACD,QAAM,gBAAgB,sBAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,gBAAkC,OAAQ,gBAAiB,EAAE;AAAA,IAClE;AAAA,IACA;AAAA,EACD;AAEA,QAAM,WAAW,iBAAiB;AAClC,QAAM,OAAO,aAAa,QAAQ;AAClC,QAAM,SAAS,aAAa,UAAU;AAEtC,SAAO;AAAA,IACN;AAAA,MACC,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,13 @@
1
1
  // packages/views/src/use-view.ts
2
2
  import { dequal } from "dequal";
3
- import { generatePreferenceKey } from "./preference-keys.mjs";
4
3
  import { useCallback, useMemo } from "@wordpress/element";
5
4
  import { useDispatch, useSelect } from "@wordpress/data";
6
5
  import { store as preferencesStore } from "@wordpress/preferences";
6
+ import { generatePreferenceKey } from "./preference-keys.mjs";
7
+ import {
8
+ mergeActiveViewOverrides,
9
+ stripActiveViewOverrides
10
+ } from "./filter-utils.mjs";
7
11
  function omit(obj, keys) {
8
12
  const result = { ...obj };
9
13
  for (const key of keys) {
@@ -12,7 +16,15 @@ function omit(obj, keys) {
12
16
  return result;
13
17
  }
14
18
  function useView(config) {
15
- const { kind, name, slug, defaultView, queryParams, onChangeQueryParams } = config;
19
+ const {
20
+ kind,
21
+ name,
22
+ slug,
23
+ defaultView,
24
+ activeViewOverrides,
25
+ queryParams,
26
+ onChangeQueryParams
27
+ } = config;
16
28
  const preferenceKey = generatePreferenceKey(kind, name, slug);
17
29
  const persistedView = useSelect(
18
30
  (select) => {
@@ -28,12 +40,16 @@ function useView(config) {
28
40
  const page = Number(queryParams?.page ?? baseView.page ?? 1);
29
41
  const search = queryParams?.search ?? baseView.search ?? "";
30
42
  const view = useMemo(() => {
31
- return {
32
- ...baseView,
33
- page,
34
- search
35
- };
36
- }, [baseView, page, search]);
43
+ return mergeActiveViewOverrides(
44
+ {
45
+ ...baseView,
46
+ page,
47
+ search
48
+ },
49
+ activeViewOverrides,
50
+ defaultView
51
+ );
52
+ }, [baseView, page, search, activeViewOverrides, defaultView]);
37
53
  const isModified = !!persistedView;
38
54
  const updateView = useCallback(
39
55
  (newView) => {
@@ -41,12 +57,26 @@ function useView(config) {
41
57
  page: newView?.page,
42
58
  search: newView?.search
43
59
  };
44
- const preferenceView = omit(newView, ["page", "search"]);
60
+ const preferenceView = stripActiveViewOverrides(
61
+ omit(newView, ["page", "search"]),
62
+ activeViewOverrides,
63
+ defaultView
64
+ );
45
65
  if (onChangeQueryParams && !dequal(urlParams, { page, search })) {
46
66
  onChangeQueryParams(urlParams);
47
67
  }
48
- if (!dequal(baseView, preferenceView)) {
49
- if (dequal(preferenceView, defaultView)) {
68
+ const comparableBaseView = stripActiveViewOverrides(
69
+ baseView,
70
+ activeViewOverrides,
71
+ defaultView
72
+ );
73
+ const comparableDefaultView = stripActiveViewOverrides(
74
+ defaultView,
75
+ activeViewOverrides,
76
+ defaultView
77
+ );
78
+ if (!dequal(comparableBaseView, preferenceView)) {
79
+ if (dequal(preferenceView, comparableDefaultView)) {
50
80
  set("core/views", preferenceKey, void 0);
51
81
  } else {
52
82
  set("core/views", preferenceKey, preferenceView);
@@ -59,6 +89,7 @@ function useView(config) {
59
89
  search,
60
90
  baseView,
61
91
  defaultView,
92
+ activeViewOverrides,
62
93
  set,
63
94
  preferenceKey
64
95
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/use-view.ts"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport { dequal } from 'dequal';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport type { ViewConfig } from './types';\n\n/**\n * WordPress dependencies\n */\nimport { useCallback, useMemo } from '@wordpress/element';\nimport { useDispatch, useSelect } from '@wordpress/data';\nimport type { View } from '@wordpress/dataviews';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\n\ninterface UseViewReturn {\n\tview: View;\n\tisModified: boolean;\n\tupdateView: ( newView: View ) => void;\n\tresetToDefault: () => void;\n}\n\nfunction omit< T extends object, K extends keyof T >(\n\tobj: T,\n\tkeys: K[]\n): Omit< T, K > {\n\tconst result = { ...obj };\n\tfor ( const key of keys ) {\n\t\tdelete result[ key ];\n\t}\n\treturn result;\n}\n\n/**\n * Hook for managing DataViews view state with local persistence.\n *\n * @param config Configuration object for loading the view.\n * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').\n * @param config.name Specific entity name.\n * @param config.slug View identifier.\n * @param config.defaultView Default view configuration.\n * @param config.queryParams Object with `page` and/or `search` from URL.\n * @param config.onChangeQueryParams Optional callback to update URL parameters.\n *\n * @return Object with current view, modification state, and update functions.\n */\nexport function useView( config: ViewConfig ): UseViewReturn {\n\tconst { kind, name, slug, defaultView, queryParams, onChangeQueryParams } =\n\t\tconfig;\n\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = useSelect(\n\t\t( select ) => {\n\t\t\treturn select( preferencesStore ).get(\n\t\t\t\t'core/views',\n\t\t\t\tpreferenceKey\n\t\t\t) as View | undefined;\n\t\t},\n\t\t[ preferenceKey ]\n\t);\n\tconst { set } = useDispatch( preferencesStore );\n\n\tconst baseView: View = persistedView ?? defaultView;\n\tconst page = Number( queryParams?.page ?? baseView.page ?? 1 );\n\tconst search = queryParams?.search ?? baseView.search ?? '';\n\n\t// Merge URL query parameters (page, search) into the view\n\tconst view: View = useMemo( () => {\n\t\treturn {\n\t\t\t...baseView,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t};\n\t}, [ baseView, page, search ] );\n\n\tconst isModified = !! persistedView;\n\n\tconst updateView = useCallback(\n\t\t( newView: View ) => {\n\t\t\t// Extract URL params (page, search) from the new view\n\t\t\tconst urlParams: { page?: number; search?: string } = {\n\t\t\t\tpage: newView?.page,\n\t\t\t\tsearch: newView?.search,\n\t\t\t};\n\t\t\tconst preferenceView = omit( newView, [ 'page', 'search' ] );\n\n\t\t\t// If we have URL handling enabled, separate URL state from preference state\n\t\t\tif (\n\t\t\t\tonChangeQueryParams &&\n\t\t\t\t! dequal( urlParams, { page, search } )\n\t\t\t) {\n\t\t\t\tonChangeQueryParams( urlParams );\n\t\t\t}\n\n\t\t\t// Only persist non-URL preferences if different from baseView\n\t\t\tif ( ! dequal( baseView, preferenceView ) ) {\n\t\t\t\tif ( dequal( preferenceView, defaultView ) ) {\n\t\t\t\t\tset( 'core/views', preferenceKey, undefined );\n\t\t\t\t} else {\n\t\t\t\t\tset( 'core/views', preferenceKey, preferenceView );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tonChangeQueryParams,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t\tbaseView,\n\t\t\tdefaultView,\n\t\t\tset,\n\t\t\tpreferenceKey,\n\t\t]\n\t);\n\n\tconst resetToDefault = useCallback( () => {\n\t\tset( 'core/views', preferenceKey, undefined );\n\t}, [ preferenceKey, set ] );\n\n\treturn {\n\t\tview,\n\t\tisModified,\n\t\tupdateView,\n\t\tresetToDefault,\n\t};\n}\n"],
5
- "mappings": ";AAGA,SAAS,cAAc;AAKvB,SAAS,6BAA6B;AAMtC,SAAS,aAAa,eAAe;AACrC,SAAS,aAAa,iBAAiB;AAGvC,SAAS,SAAS,wBAAwB;AAS1C,SAAS,KACR,KACA,MACe;AACf,QAAM,SAAS,EAAE,GAAG,IAAI;AACxB,aAAY,OAAO,MAAO;AACzB,WAAO,OAAQ,GAAI;AAAA,EACpB;AACA,SAAO;AACR;AAeO,SAAS,QAAS,QAAoC;AAC5D,QAAM,EAAE,MAAM,MAAM,MAAM,aAAa,aAAa,oBAAoB,IACvE;AAED,QAAM,gBAAgB,sBAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,gBAAkC;AAAA,IACvC,CAAE,WAAY;AACb,aAAO,OAAQ,gBAAiB,EAAE;AAAA,QACjC;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,IACA,CAAE,aAAc;AAAA,EACjB;AACA,QAAM,EAAE,IAAI,IAAI,YAAa,gBAAiB;AAE9C,QAAM,WAAiB,iBAAiB;AACxC,QAAM,OAAO,OAAQ,aAAa,QAAQ,SAAS,QAAQ,CAAE;AAC7D,QAAM,SAAS,aAAa,UAAU,SAAS,UAAU;AAGzD,QAAM,OAAa,QAAS,MAAM;AACjC,WAAO;AAAA,MACN,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACD;AAAA,EACD,GAAG,CAAE,UAAU,MAAM,MAAO,CAAE;AAE9B,QAAM,aAAa,CAAC,CAAE;AAEtB,QAAM,aAAa;AAAA,IAClB,CAAE,YAAmB;AAEpB,YAAM,YAAgD;AAAA,QACrD,MAAM,SAAS;AAAA,QACf,QAAQ,SAAS;AAAA,MAClB;AACA,YAAM,iBAAiB,KAAM,SAAS,CAAE,QAAQ,QAAS,CAAE;AAG3D,UACC,uBACA,CAAE,OAAQ,WAAW,EAAE,MAAM,OAAO,CAAE,GACrC;AACD,4BAAqB,SAAU;AAAA,MAChC;AAGA,UAAK,CAAE,OAAQ,UAAU,cAAe,GAAI;AAC3C,YAAK,OAAQ,gBAAgB,WAAY,GAAI;AAC5C,cAAK,cAAc,eAAe,MAAU;AAAA,QAC7C,OAAO;AACN,cAAK,cAAc,eAAe,cAAe;AAAA,QAClD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAEA,QAAM,iBAAiB,YAAa,MAAM;AACzC,QAAK,cAAc,eAAe,MAAU;AAAA,EAC7C,GAAG,CAAE,eAAe,GAAI,CAAE;AAE1B,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport { dequal } from 'dequal';\n\n/**\n * WordPress dependencies\n */\nimport { useCallback, useMemo } from '@wordpress/element';\nimport { useDispatch, useSelect } from '@wordpress/data';\nimport type { View } from '@wordpress/dataviews';\n// @ts-ignore - Preferences package is not typed\nimport { store as preferencesStore } from '@wordpress/preferences';\n\n/**\n * Internal dependencies\n */\nimport { generatePreferenceKey } from './preference-keys';\nimport {\n\tmergeActiveViewOverrides,\n\tstripActiveViewOverrides,\n} from './filter-utils';\nimport type { ViewConfig } from './types';\n\ninterface UseViewReturn {\n\tview: View;\n\tisModified: boolean;\n\tupdateView: ( newView: View ) => void;\n\tresetToDefault: () => void;\n}\n\nfunction omit< T extends object, K extends keyof T >(\n\tobj: T,\n\tkeys: K[]\n): Omit< T, K > {\n\tconst result = { ...obj };\n\tfor ( const key of keys ) {\n\t\tdelete result[ key ];\n\t}\n\treturn result;\n}\n\n/**\n * Hook for managing DataViews view state with local persistence.\n *\n * @param config Configuration object for loading the view.\n *\n * @return Object with current view, modification state, and update functions.\n */\nexport function useView( config: ViewConfig ): UseViewReturn {\n\tconst {\n\t\tkind,\n\t\tname,\n\t\tslug,\n\t\tdefaultView,\n\t\tactiveViewOverrides,\n\t\tqueryParams,\n\t\tonChangeQueryParams,\n\t} = config;\n\n\tconst preferenceKey = generatePreferenceKey( kind, name, slug );\n\tconst persistedView: View | undefined = useSelect(\n\t\t( select ) => {\n\t\t\treturn select( preferencesStore ).get(\n\t\t\t\t'core/views',\n\t\t\t\tpreferenceKey\n\t\t\t) as View | undefined;\n\t\t},\n\t\t[ preferenceKey ]\n\t);\n\tconst { set } = useDispatch( preferencesStore );\n\n\tconst baseView: View = persistedView ?? defaultView;\n\tconst page = Number( queryParams?.page ?? baseView.page ?? 1 );\n\tconst search = queryParams?.search ?? baseView.search ?? '';\n\n\t// Merge URL query parameters (page, search) and activeViewOverrides into the view\n\tconst view: View = useMemo( () => {\n\t\treturn mergeActiveViewOverrides(\n\t\t\t{\n\t\t\t\t...baseView,\n\t\t\t\tpage,\n\t\t\t\tsearch,\n\t\t\t},\n\t\t\tactiveViewOverrides,\n\t\t\tdefaultView\n\t\t);\n\t}, [ baseView, page, search, activeViewOverrides, defaultView ] );\n\n\tconst isModified = !! persistedView;\n\n\tconst updateView = useCallback(\n\t\t( newView: View ) => {\n\t\t\t// Extract URL params (page, search) from the new view\n\t\t\tconst urlParams: { page?: number; search?: string } = {\n\t\t\t\tpage: newView?.page,\n\t\t\t\tsearch: newView?.search,\n\t\t\t};\n\t\t\t// Strip activeViewOverrides and URL params before persisting\n\t\t\t// Cast is safe: omitting page/search doesn't change the discriminant (type field)\n\t\t\tconst preferenceView = stripActiveViewOverrides(\n\t\t\t\tomit( newView, [ 'page', 'search' ] ) as View,\n\t\t\t\tactiveViewOverrides,\n\t\t\t\tdefaultView\n\t\t\t);\n\n\t\t\t// If we have URL handling enabled, separate URL state from preference state\n\t\t\tif (\n\t\t\t\tonChangeQueryParams &&\n\t\t\t\t! dequal( urlParams, { page, search } )\n\t\t\t) {\n\t\t\t\tonChangeQueryParams( urlParams );\n\t\t\t}\n\n\t\t\t// Compare with baseView and defaultView after stripping activeViewOverrides\n\t\t\tconst comparableBaseView = stripActiveViewOverrides(\n\t\t\t\tbaseView,\n\t\t\t\tactiveViewOverrides,\n\t\t\t\tdefaultView\n\t\t\t);\n\t\t\tconst comparableDefaultView = stripActiveViewOverrides(\n\t\t\t\tdefaultView,\n\t\t\t\tactiveViewOverrides,\n\t\t\t\tdefaultView\n\t\t\t);\n\n\t\t\t// Only persist non-URL preferences if different from baseView\n\t\t\tif ( ! dequal( comparableBaseView, preferenceView ) ) {\n\t\t\t\tif ( dequal( preferenceView, comparableDefaultView ) ) {\n\t\t\t\t\tset( 'core/views', preferenceKey, undefined );\n\t\t\t\t} else {\n\t\t\t\t\tset( 'core/views', preferenceKey, preferenceView );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tonChangeQueryParams,\n\t\t\tpage,\n\t\t\tsearch,\n\t\t\tbaseView,\n\t\t\tdefaultView,\n\t\t\tactiveViewOverrides,\n\t\t\tset,\n\t\t\tpreferenceKey,\n\t\t]\n\t);\n\n\tconst resetToDefault = useCallback( () => {\n\t\tset( 'core/views', preferenceKey, undefined );\n\t}, [ preferenceKey, set ] );\n\n\treturn {\n\t\tview,\n\t\tisModified,\n\t\tupdateView,\n\t\tresetToDefault,\n\t};\n}\n"],
5
+ "mappings": ";AAGA,SAAS,cAAc;AAKvB,SAAS,aAAa,eAAe;AACrC,SAAS,aAAa,iBAAiB;AAGvC,SAAS,SAAS,wBAAwB;AAK1C,SAAS,6BAA6B;AACtC;AAAA,EACC;AAAA,EACA;AAAA,OACM;AAUP,SAAS,KACR,KACA,MACe;AACf,QAAM,SAAS,EAAE,GAAG,IAAI;AACxB,aAAY,OAAO,MAAO;AACzB,WAAO,OAAQ,GAAI;AAAA,EACpB;AACA,SAAO;AACR;AASO,SAAS,QAAS,QAAoC;AAC5D,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,QAAM,gBAAgB,sBAAuB,MAAM,MAAM,IAAK;AAC9D,QAAM,gBAAkC;AAAA,IACvC,CAAE,WAAY;AACb,aAAO,OAAQ,gBAAiB,EAAE;AAAA,QACjC;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,IACA,CAAE,aAAc;AAAA,EACjB;AACA,QAAM,EAAE,IAAI,IAAI,YAAa,gBAAiB;AAE9C,QAAM,WAAiB,iBAAiB;AACxC,QAAM,OAAO,OAAQ,aAAa,QAAQ,SAAS,QAAQ,CAAE;AAC7D,QAAM,SAAS,aAAa,UAAU,SAAS,UAAU;AAGzD,QAAM,OAAa,QAAS,MAAM;AACjC,WAAO;AAAA,MACN;AAAA,QACC,GAAG;AAAA,QACH;AAAA,QACA;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD,GAAG,CAAE,UAAU,MAAM,QAAQ,qBAAqB,WAAY,CAAE;AAEhE,QAAM,aAAa,CAAC,CAAE;AAEtB,QAAM,aAAa;AAAA,IAClB,CAAE,YAAmB;AAEpB,YAAM,YAAgD;AAAA,QACrD,MAAM,SAAS;AAAA,QACf,QAAQ,SAAS;AAAA,MAClB;AAGA,YAAM,iBAAiB;AAAA,QACtB,KAAM,SAAS,CAAE,QAAQ,QAAS,CAAE;AAAA,QACpC;AAAA,QACA;AAAA,MACD;AAGA,UACC,uBACA,CAAE,OAAQ,WAAW,EAAE,MAAM,OAAO,CAAE,GACrC;AACD,4BAAqB,SAAU;AAAA,MAChC;AAGA,YAAM,qBAAqB;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,MACD;AACA,YAAM,wBAAwB;AAAA,QAC7B;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAGA,UAAK,CAAE,OAAQ,oBAAoB,cAAe,GAAI;AACrD,YAAK,OAAQ,gBAAgB,qBAAsB,GAAI;AACtD,cAAK,cAAc,eAAe,MAAU;AAAA,QAC7C,OAAO;AACN,cAAK,cAAc,eAAe,cAAe;AAAA,QAClD;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAEA,QAAM,iBAAiB,YAAa,MAAM;AACzC,QAAK,cAAc,eAAe,MAAU;AAAA,EAC7C,GAAG,CAAE,eAAe,GAAI,CAAE;AAE1B,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import type { View, Filter } from '@wordpress/dataviews';
5
+ type ActiveViewOverrides = {
6
+ filters?: Filter[];
7
+ sort?: View['sort'];
8
+ };
9
+ /**
10
+ * Merges activeViewOverrides into a view.
11
+ * Filters: Active filters take precedence; same-field filters are replaced.
12
+ * Sort: Active sort is applied only if current sort matches the default.
13
+ *
14
+ * @param view The view to merge overrides into.
15
+ * @param activeViewOverrides The tab-specific overrides to apply.
16
+ * @param defaultView The default view configuration.
17
+ * @return A new view with merged overrides, or the original view if no overrides.
18
+ */
19
+ export declare function mergeActiveViewOverrides(view: View, activeViewOverrides?: ActiveViewOverrides, defaultView?: View): View;
20
+ /**
21
+ * Strips overrides before persisting.
22
+ * Filters: Removes filters on fields managed by activeViewOverrides.
23
+ * Sort: If sort matches the override, restores the default sort.
24
+ *
25
+ * @param view The view to strip overrides from.
26
+ * @param activeViewOverrides The tab-specific override definitions.
27
+ * @param defaultView The default view configuration.
28
+ * @return A new view with overrides stripped, or the original view if no overrides.
29
+ */
30
+ export declare function stripActiveViewOverrides(view: View, activeViewOverrides?: ActiveViewOverrides, defaultView?: View): View;
31
+ export {};
32
+ //# sourceMappingURL=filter-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-utils.d.ts","sourceRoot":"","sources":["../src/filter-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEzD,KAAK,mBAAmB,GAAG;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,CAAE,MAAM,CAAE,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,IAAI,EACV,mBAAmB,CAAC,EAAE,mBAAmB,EACzC,WAAW,CAAC,EAAE,IAAI,GAChB,IAAI,CAwCN;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,IAAI,EACV,mBAAmB,CAAC,EAAE,mBAAmB,EACzC,WAAW,CAAC,EAAE,IAAI,GAChB,IAAI,CAoCN"}
@@ -1,196 +1,16 @@
1
+ import type { View } from '@wordpress/dataviews';
1
2
  import type { ViewConfig } from './types';
2
3
  /**
3
- * Async function for loading view state in route loaders with optional URL parameters.
4
- *
5
- * @example
6
- *
7
- * ```typescript
8
- * // In route loader
9
- * const view = await loadView( {
10
- * kind: 'taxonomy',
11
- * name: 'category',
12
- * slug: 'all',
13
- * defaultView,
14
- * queryParams: { page: search.page, search: search.search },
15
- * } );
16
- * ```
17
- *
18
- * @param config Configuration object for loading the view.
19
- * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').
20
- * @param config.name Specific entity name.
21
- * @param config.slug View identifier.
22
- * @param config.defaultView Default view configuration.
23
- * @param config.queryParams Object with `page` and/or `search` from URL.
4
+ * Async function for loading view state in route loaders.
24
5
  *
6
+ * @param config Configuration object for loading the view.
7
+ * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').
8
+ * @param config.name Specific entity name.
9
+ * @param config.slug View identifier.
10
+ * @param config.defaultView Default view configuration.
11
+ * @param config.activeViewOverrides View overrides applied on top but never persisted.
12
+ * @param config.queryParams Object with `page` and/or `search` from URL.
25
13
  * @return Promise resolving to the loaded view object.
26
14
  */
27
- export declare function loadView(config: ViewConfig): Promise<{
28
- page: number;
29
- search: string;
30
- type: "list";
31
- layout?: {
32
- density?: import("@wordpress/dataviews").Density;
33
- };
34
- filters?: import("@wordpress/dataviews").Filter[];
35
- sort?: {
36
- field: string;
37
- direction: import("@wordpress/dataviews").SortDirection;
38
- };
39
- perPage?: number;
40
- fields?: string[];
41
- titleField?: string;
42
- mediaField?: string;
43
- descriptionField?: string;
44
- showTitle?: boolean;
45
- showMedia?: boolean;
46
- showDescription?: boolean;
47
- showLevels?: boolean;
48
- groupBy?: {
49
- field: string;
50
- direction: import("@wordpress/dataviews").SortDirection;
51
- showLabel?: boolean;
52
- };
53
- infiniteScrollEnabled?: boolean;
54
- } | {
55
- page: number;
56
- search: string;
57
- type: "grid";
58
- layout?: {
59
- badgeFields?: string[];
60
- previewSize?: number;
61
- };
62
- filters?: import("@wordpress/dataviews").Filter[];
63
- sort?: {
64
- field: string;
65
- direction: import("@wordpress/dataviews").SortDirection;
66
- };
67
- perPage?: number;
68
- fields?: string[];
69
- titleField?: string;
70
- mediaField?: string;
71
- descriptionField?: string;
72
- showTitle?: boolean;
73
- showMedia?: boolean;
74
- showDescription?: boolean;
75
- showLevels?: boolean;
76
- groupBy?: {
77
- field: string;
78
- direction: import("@wordpress/dataviews").SortDirection;
79
- showLabel?: boolean;
80
- };
81
- infiniteScrollEnabled?: boolean;
82
- } | {
83
- page: number;
84
- search: string;
85
- type: "table";
86
- layout?: {
87
- styles?: Record<string, import("@wordpress/dataviews").ColumnStyle>;
88
- density?: import("@wordpress/dataviews").Density;
89
- enableMoving?: boolean;
90
- };
91
- filters?: import("@wordpress/dataviews").Filter[];
92
- sort?: {
93
- field: string;
94
- direction: import("@wordpress/dataviews").SortDirection;
95
- };
96
- perPage?: number;
97
- fields?: string[];
98
- titleField?: string;
99
- mediaField?: string;
100
- descriptionField?: string;
101
- showTitle?: boolean;
102
- showMedia?: boolean;
103
- showDescription?: boolean;
104
- showLevels?: boolean;
105
- groupBy?: {
106
- field: string;
107
- direction: import("@wordpress/dataviews").SortDirection;
108
- showLabel?: boolean;
109
- };
110
- infiniteScrollEnabled?: boolean;
111
- } | {
112
- page: number;
113
- search: string;
114
- type: "pickerGrid";
115
- layout?: {
116
- badgeFields?: string[];
117
- previewSize?: number;
118
- };
119
- filters?: import("@wordpress/dataviews").Filter[];
120
- sort?: {
121
- field: string;
122
- direction: import("@wordpress/dataviews").SortDirection;
123
- };
124
- perPage?: number;
125
- fields?: string[];
126
- titleField?: string;
127
- mediaField?: string;
128
- descriptionField?: string;
129
- showTitle?: boolean;
130
- showMedia?: boolean;
131
- showDescription?: boolean;
132
- showLevels?: boolean;
133
- groupBy?: {
134
- field: string;
135
- direction: import("@wordpress/dataviews").SortDirection;
136
- showLabel?: boolean;
137
- };
138
- infiniteScrollEnabled?: boolean;
139
- } | {
140
- page: number;
141
- search: string;
142
- type: "pickerTable";
143
- layout?: {
144
- styles?: Record<string, import("@wordpress/dataviews").ColumnStyle>;
145
- density?: import("@wordpress/dataviews").Density;
146
- enableMoving?: boolean;
147
- };
148
- filters?: import("@wordpress/dataviews").Filter[];
149
- sort?: {
150
- field: string;
151
- direction: import("@wordpress/dataviews").SortDirection;
152
- };
153
- perPage?: number;
154
- fields?: string[];
155
- titleField?: string;
156
- mediaField?: string;
157
- descriptionField?: string;
158
- showTitle?: boolean;
159
- showMedia?: boolean;
160
- showDescription?: boolean;
161
- showLevels?: boolean;
162
- groupBy?: {
163
- field: string;
164
- direction: import("@wordpress/dataviews").SortDirection;
165
- showLabel?: boolean;
166
- };
167
- infiniteScrollEnabled?: boolean;
168
- } | {
169
- page: number;
170
- search: string;
171
- type: "activity";
172
- layout?: {
173
- density?: import("@wordpress/dataviews").Density;
174
- };
175
- filters?: import("@wordpress/dataviews").Filter[];
176
- sort?: {
177
- field: string;
178
- direction: import("@wordpress/dataviews").SortDirection;
179
- };
180
- perPage?: number;
181
- fields?: string[];
182
- titleField?: string;
183
- mediaField?: string;
184
- descriptionField?: string;
185
- showTitle?: boolean;
186
- showMedia?: boolean;
187
- showDescription?: boolean;
188
- showLevels?: boolean;
189
- groupBy?: {
190
- field: string;
191
- direction: import("@wordpress/dataviews").SortDirection;
192
- showLabel?: boolean;
193
- };
194
- infiniteScrollEnabled?: boolean;
195
- }>;
15
+ export declare function loadView(config: ViewConfig): Promise<View>;
196
16
  //# sourceMappingURL=load-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-view.d.ts","sourceRoot":"","sources":["../src/load-view.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,QAAQ,CAAE,MAAM,EAAE,UAAU;;;;;eAkB0jF,CAAC;;;;;;;;;;;;;;;;;;;iBAAxnC,CAAC;;;;;;;;mBAAw+C,CAAC;mBAAgG,CAAC;;;;;;;;;;;;;;;;;;;iBAA3kD,CAAC;;;;;;;;cAAmvB,CAAC;eAA0G,CAAC;oBAAyG,CAAC;;;;;;;;;;;;;;;;;;;iBAA18B,CAAC;;;;;;;;mBAAgxD,CAAC;mBAAgG,CAAC;;;;;;;;;;;;;;;;;;;iBAAn3D,CAAC;;;;;;;;cAA8iE,CAAC;eAA0G,CAAC;oBAAyG,CAAC;;;;;;;;;;;;;;;;;;;iBAArwE,CAAC;;;;;;;;eAA4yC,CAAC;;;;;;;;;;;;;;;;;;;iBAA9yC,CAAC;;;GADr/C"}
1
+ {"version":3,"file":"load-view.d.ts","sourceRoot":"","sources":["../src/load-view.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAOjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAE,MAAM,EAAE,UAAU,iBAsBjD"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import type { View } from '@wordpress/dataviews';
4
+ import type { View, Filter } from '@wordpress/dataviews';
5
5
  export interface ViewConfig {
6
6
  /**
7
7
  * Entity kind (e.g. postType, root).
@@ -21,6 +21,15 @@ export interface ViewConfig {
21
21
  * Default view configuration
22
22
  */
23
23
  defaultView: View;
24
+ /**
25
+ * View overrides applied on top of the persisted view but never persisted.
26
+ * These represent tab-specific configuration (filters, sort) that should
27
+ * override the persisted view settings.
28
+ */
29
+ activeViewOverrides?: {
30
+ filters?: Filter[];
31
+ sort?: View['sort'];
32
+ };
24
33
  /**
25
34
  * Optional query parameters from URL (page, search)
26
35
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEjD,MAAM,WAAW,UAAU;IAC1B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,IAAI,CAAC;IAElB;;OAEG;IACH,WAAW,CAAC,EAAE;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;OAEG;IACH,mBAAmB,CAAC,EAAE,CAAE,MAAM,EAAE;QAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,KAAM,IAAI,CAAC;CACZ"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,UAAU;IAC1B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,IAAI,CAAC;IAElB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE;QACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE,IAAI,CAAE,MAAM,CAAE,CAAC;KACtB,CAAC;IAEF;;OAEG;IACH,WAAW,CAAC,EAAE;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;OAEG;IACH,mBAAmB,CAAC,EAAE,CAAE,MAAM,EAAE;QAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,KAAM,IAAI,CAAC;CACZ"}
@@ -1,5 +1,5 @@
1
- import type { ViewConfig } from './types';
2
1
  import type { View } from '@wordpress/dataviews';
2
+ import type { ViewConfig } from './types';
3
3
  interface UseViewReturn {
4
4
  view: View;
5
5
  isModified: boolean;
@@ -9,13 +9,7 @@ interface UseViewReturn {
9
9
  /**
10
10
  * Hook for managing DataViews view state with local persistence.
11
11
  *
12
- * @param config Configuration object for loading the view.
13
- * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').
14
- * @param config.name Specific entity name.
15
- * @param config.slug View identifier.
16
- * @param config.defaultView Default view configuration.
17
- * @param config.queryParams Object with `page` and/or `search` from URL.
18
- * @param config.onChangeQueryParams Optional callback to update URL parameters.
12
+ * @param config Configuration object for loading the view.
19
13
  *
20
14
  * @return Object with current view, modification state, and update functions.
21
15
  */
@@ -1 +1 @@
1
- {"version":3,"file":"use-view.d.ts","sourceRoot":"","sources":["../src/use-view.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAO1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAIjD,UAAU,aAAa;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,CAAE,OAAO,EAAE,IAAI,KAAM,IAAI,CAAC;IACtC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC3B;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAE,MAAM,EAAE,UAAU,GAAI,aAAa,CA8E3D"}
1
+ {"version":3,"file":"use-view.d.ts","sourceRoot":"","sources":["../src/use-view.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAYjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,UAAU,aAAa;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,CAAE,OAAO,EAAE,IAAI,KAAM,IAAI,CAAC;IACtC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC3B;AAaD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAE,MAAM,EAAE,UAAU,GAAI,aAAa,CA4G3D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/views",
3
- "version": "1.6.0",
3
+ "version": "1.6.1-next.v.20260206T143.0+81f8de885",
4
4
  "description": "View persistence and management for WordPress DataViews.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -41,14 +41,14 @@
41
41
  },
42
42
  "types": "build-types",
43
43
  "dependencies": {
44
- "@wordpress/data": "^10.39.0",
45
- "@wordpress/dataviews": "^11.3.0",
46
- "@wordpress/element": "^6.39.0",
47
- "@wordpress/preferences": "^4.39.0",
44
+ "@wordpress/data": "^10.39.1-next.v.20260206T143.0+81f8de885",
45
+ "@wordpress/dataviews": "^12.0.1-next.v.20260206T143.0+81f8de885",
46
+ "@wordpress/element": "^6.39.1-next.v.20260206T143.0+81f8de885",
47
+ "@wordpress/preferences": "^4.39.1-next.v.20260206T143.0+81f8de885",
48
48
  "dequal": "^2.0.3"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
52
52
  },
53
- "gitHead": "eee1cfb1472f11183e40fb77465a5f13145df7ad"
53
+ "gitHead": "6ae3fbb907e6c309ff6a0685e5e5ff0c2ee23b15"
54
54
  }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import type { View, Filter } from '@wordpress/dataviews';
5
+
6
+ type ActiveViewOverrides = {
7
+ filters?: Filter[];
8
+ sort?: View[ 'sort' ];
9
+ };
10
+
11
+ /**
12
+ * Merges activeViewOverrides into a view.
13
+ * Filters: Active filters take precedence; same-field filters are replaced.
14
+ * Sort: Active sort is applied only if current sort matches the default.
15
+ *
16
+ * @param view The view to merge overrides into.
17
+ * @param activeViewOverrides The tab-specific overrides to apply.
18
+ * @param defaultView The default view configuration.
19
+ * @return A new view with merged overrides, or the original view if no overrides.
20
+ */
21
+ export function mergeActiveViewOverrides(
22
+ view: View,
23
+ activeViewOverrides?: ActiveViewOverrides,
24
+ defaultView?: View
25
+ ): View {
26
+ if ( ! activeViewOverrides ) {
27
+ return view;
28
+ }
29
+
30
+ let result = view;
31
+
32
+ // Merge filters
33
+ if (
34
+ activeViewOverrides.filters &&
35
+ activeViewOverrides.filters.length > 0
36
+ ) {
37
+ const activeFields = new Set(
38
+ activeViewOverrides.filters.map( ( f ) => f.field )
39
+ );
40
+ const preserved = ( view.filters ?? [] ).filter(
41
+ ( f: Filter ) => ! activeFields.has( f.field )
42
+ );
43
+ result = {
44
+ ...result,
45
+ filters: [ ...preserved, ...activeViewOverrides.filters ],
46
+ };
47
+ }
48
+
49
+ // Merge sort - only apply if the current sort matches the default
50
+ if ( activeViewOverrides.sort ) {
51
+ const isDefaultSort =
52
+ defaultView &&
53
+ view.sort?.field === defaultView.sort?.field &&
54
+ view.sort?.direction === defaultView.sort?.direction;
55
+
56
+ if ( isDefaultSort ) {
57
+ result = {
58
+ ...result,
59
+ sort: activeViewOverrides.sort,
60
+ };
61
+ }
62
+ }
63
+
64
+ return result;
65
+ }
66
+
67
+ /**
68
+ * Strips overrides before persisting.
69
+ * Filters: Removes filters on fields managed by activeViewOverrides.
70
+ * Sort: If sort matches the override, restores the default sort.
71
+ *
72
+ * @param view The view to strip overrides from.
73
+ * @param activeViewOverrides The tab-specific override definitions.
74
+ * @param defaultView The default view configuration.
75
+ * @return A new view with overrides stripped, or the original view if no overrides.
76
+ */
77
+ export function stripActiveViewOverrides(
78
+ view: View,
79
+ activeViewOverrides?: ActiveViewOverrides,
80
+ defaultView?: View
81
+ ): View {
82
+ if ( ! activeViewOverrides ) {
83
+ return view;
84
+ }
85
+
86
+ let result = view;
87
+
88
+ // Strip managed filters
89
+ if (
90
+ activeViewOverrides.filters &&
91
+ activeViewOverrides.filters.length > 0
92
+ ) {
93
+ const activeFields = new Set(
94
+ activeViewOverrides.filters.map( ( f ) => f.field )
95
+ );
96
+ result = {
97
+ ...result,
98
+ filters: ( view.filters ?? [] ).filter(
99
+ ( f: Filter ) => ! activeFields.has( f.field )
100
+ ),
101
+ };
102
+ }
103
+
104
+ // Strip sort if it matches the override (restore to default)
105
+ if (
106
+ activeViewOverrides.sort &&
107
+ view.sort?.field === activeViewOverrides.sort.field &&
108
+ view.sort?.direction === activeViewOverrides.sort.direction
109
+ ) {
110
+ result = {
111
+ ...result,
112
+ sort: defaultView?.sort,
113
+ };
114
+ }
115
+
116
+ return result;
117
+ }
package/src/load-view.ts CHANGED
@@ -10,35 +10,24 @@ import type { View } from '@wordpress/dataviews';
10
10
  * Internal dependencies
11
11
  */
12
12
  import { generatePreferenceKey } from './preference-keys';
13
+ import { mergeActiveViewOverrides } from './filter-utils';
13
14
  import type { ViewConfig } from './types';
14
15
 
15
16
  /**
16
- * Async function for loading view state in route loaders with optional URL parameters.
17
- *
18
- * @example
19
- *
20
- * ```typescript
21
- * // In route loader
22
- * const view = await loadView( {
23
- * kind: 'taxonomy',
24
- * name: 'category',
25
- * slug: 'all',
26
- * defaultView,
27
- * queryParams: { page: search.page, search: search.search },
28
- * } );
29
- * ```
30
- *
31
- * @param config Configuration object for loading the view.
32
- * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').
33
- * @param config.name Specific entity name.
34
- * @param config.slug View identifier.
35
- * @param config.defaultView Default view configuration.
36
- * @param config.queryParams Object with `page` and/or `search` from URL.
17
+ * Async function for loading view state in route loaders.
37
18
  *
19
+ * @param config Configuration object for loading the view.
20
+ * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').
21
+ * @param config.name Specific entity name.
22
+ * @param config.slug View identifier.
23
+ * @param config.defaultView Default view configuration.
24
+ * @param config.activeViewOverrides View overrides applied on top but never persisted.
25
+ * @param config.queryParams Object with `page` and/or `search` from URL.
38
26
  * @return Promise resolving to the loaded view object.
39
27
  */
40
28
  export async function loadView( config: ViewConfig ) {
41
- const { kind, name, slug, defaultView, queryParams } = config;
29
+ const { kind, name, slug, defaultView, activeViewOverrides, queryParams } =
30
+ config;
42
31
  const preferenceKey = generatePreferenceKey( kind, name, slug );
43
32
  const persistedView: View | undefined = select( preferencesStore ).get(
44
33
  'core/views',
@@ -49,9 +38,13 @@ export async function loadView( config: ViewConfig ) {
49
38
  const page = queryParams?.page ?? 1;
50
39
  const search = queryParams?.search ?? '';
51
40
 
52
- return {
53
- ...baseView,
54
- page,
55
- search,
56
- };
41
+ return mergeActiveViewOverrides(
42
+ {
43
+ ...baseView,
44
+ page,
45
+ search,
46
+ },
47
+ activeViewOverrides,
48
+ defaultView
49
+ );
57
50
  }
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import type { View } from '@wordpress/dataviews';
4
+ import type { View, Filter } from '@wordpress/dataviews';
5
5
 
6
6
  export interface ViewConfig {
7
7
  /**
@@ -26,6 +26,16 @@ export interface ViewConfig {
26
26
  */
27
27
  defaultView: View;
28
28
 
29
+ /**
30
+ * View overrides applied on top of the persisted view but never persisted.
31
+ * These represent tab-specific configuration (filters, sort) that should
32
+ * override the persisted view settings.
33
+ */
34
+ activeViewOverrides?: {
35
+ filters?: Filter[];
36
+ sort?: View[ 'sort' ];
37
+ };
38
+
29
39
  /**
30
40
  * Optional query parameters from URL (page, search)
31
41
  */
package/src/use-view.ts CHANGED
@@ -3,12 +3,6 @@
3
3
  */
4
4
  import { dequal } from 'dequal';
5
5
 
6
- /**
7
- * Internal dependencies
8
- */
9
- import { generatePreferenceKey } from './preference-keys';
10
- import type { ViewConfig } from './types';
11
-
12
6
  /**
13
7
  * WordPress dependencies
14
8
  */
@@ -18,6 +12,16 @@ import type { View } from '@wordpress/dataviews';
18
12
  // @ts-ignore - Preferences package is not typed
19
13
  import { store as preferencesStore } from '@wordpress/preferences';
20
14
 
15
+ /**
16
+ * Internal dependencies
17
+ */
18
+ import { generatePreferenceKey } from './preference-keys';
19
+ import {
20
+ mergeActiveViewOverrides,
21
+ stripActiveViewOverrides,
22
+ } from './filter-utils';
23
+ import type { ViewConfig } from './types';
24
+
21
25
  interface UseViewReturn {
22
26
  view: View;
23
27
  isModified: boolean;
@@ -39,19 +43,20 @@ function omit< T extends object, K extends keyof T >(
39
43
  /**
40
44
  * Hook for managing DataViews view state with local persistence.
41
45
  *
42
- * @param config Configuration object for loading the view.
43
- * @param config.kind Entity kind (e.g., 'postType', 'taxonomy', 'root').
44
- * @param config.name Specific entity name.
45
- * @param config.slug View identifier.
46
- * @param config.defaultView Default view configuration.
47
- * @param config.queryParams Object with `page` and/or `search` from URL.
48
- * @param config.onChangeQueryParams Optional callback to update URL parameters.
46
+ * @param config Configuration object for loading the view.
49
47
  *
50
48
  * @return Object with current view, modification state, and update functions.
51
49
  */
52
50
  export function useView( config: ViewConfig ): UseViewReturn {
53
- const { kind, name, slug, defaultView, queryParams, onChangeQueryParams } =
54
- config;
51
+ const {
52
+ kind,
53
+ name,
54
+ slug,
55
+ defaultView,
56
+ activeViewOverrides,
57
+ queryParams,
58
+ onChangeQueryParams,
59
+ } = config;
55
60
 
56
61
  const preferenceKey = generatePreferenceKey( kind, name, slug );
57
62
  const persistedView: View | undefined = useSelect(
@@ -69,14 +74,18 @@ export function useView( config: ViewConfig ): UseViewReturn {
69
74
  const page = Number( queryParams?.page ?? baseView.page ?? 1 );
70
75
  const search = queryParams?.search ?? baseView.search ?? '';
71
76
 
72
- // Merge URL query parameters (page, search) into the view
77
+ // Merge URL query parameters (page, search) and activeViewOverrides into the view
73
78
  const view: View = useMemo( () => {
74
- return {
75
- ...baseView,
76
- page,
77
- search,
78
- };
79
- }, [ baseView, page, search ] );
79
+ return mergeActiveViewOverrides(
80
+ {
81
+ ...baseView,
82
+ page,
83
+ search,
84
+ },
85
+ activeViewOverrides,
86
+ defaultView
87
+ );
88
+ }, [ baseView, page, search, activeViewOverrides, defaultView ] );
80
89
 
81
90
  const isModified = !! persistedView;
82
91
 
@@ -87,7 +96,13 @@ export function useView( config: ViewConfig ): UseViewReturn {
87
96
  page: newView?.page,
88
97
  search: newView?.search,
89
98
  };
90
- const preferenceView = omit( newView, [ 'page', 'search' ] );
99
+ // Strip activeViewOverrides and URL params before persisting
100
+ // Cast is safe: omitting page/search doesn't change the discriminant (type field)
101
+ const preferenceView = stripActiveViewOverrides(
102
+ omit( newView, [ 'page', 'search' ] ) as View,
103
+ activeViewOverrides,
104
+ defaultView
105
+ );
91
106
 
92
107
  // If we have URL handling enabled, separate URL state from preference state
93
108
  if (
@@ -97,9 +112,21 @@ export function useView( config: ViewConfig ): UseViewReturn {
97
112
  onChangeQueryParams( urlParams );
98
113
  }
99
114
 
115
+ // Compare with baseView and defaultView after stripping activeViewOverrides
116
+ const comparableBaseView = stripActiveViewOverrides(
117
+ baseView,
118
+ activeViewOverrides,
119
+ defaultView
120
+ );
121
+ const comparableDefaultView = stripActiveViewOverrides(
122
+ defaultView,
123
+ activeViewOverrides,
124
+ defaultView
125
+ );
126
+
100
127
  // Only persist non-URL preferences if different from baseView
101
- if ( ! dequal( baseView, preferenceView ) ) {
102
- if ( dequal( preferenceView, defaultView ) ) {
128
+ if ( ! dequal( comparableBaseView, preferenceView ) ) {
129
+ if ( dequal( preferenceView, comparableDefaultView ) ) {
103
130
  set( 'core/views', preferenceKey, undefined );
104
131
  } else {
105
132
  set( 'core/views', preferenceKey, preferenceView );
@@ -112,6 +139,7 @@ export function useView( config: ViewConfig ): UseViewReturn {
112
139
  search,
113
140
  baseView,
114
141
  defaultView,
142
+ activeViewOverrides,
115
143
  set,
116
144
  preferenceKey,
117
145
  ]