From 9e792ad7fd98265a860ed1f30070df9beedead77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Tue, 5 Mar 2024 18:17:05 +0100
Subject: [PATCH 01/24] Implement multiple selection in filters
---
packages/dataviews/src/filter-summary.js | 29 ++++----
packages/dataviews/src/filters.js | 1 +
packages/dataviews/src/search-widget.js | 71 +++++++++++++++----
.../src/components/page-pages/index.js | 3 +
4 files changed, 75 insertions(+), 29 deletions(-)
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index 6e1e3e9e2620b..67571d7dd9535 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -26,8 +26,8 @@ import { ENTER, SPACE } from '@wordpress/keycodes';
import SearchWidget from './search-widget';
import { OPERATOR_IN, OPERATOR_NOT_IN, OPERATORS } from './constants';
-const FilterText = ( { activeElement, filterInView, filter } ) => {
- if ( activeElement === undefined ) {
+const FilterText = ( { activeElements, filterInView, filter } ) => {
+ if ( activeElements === undefined || activeElements.length === 0 ) {
return filter.name;
}
@@ -36,31 +36,25 @@ const FilterText = ( { activeElement, filterInView, filter } ) => {
Span2: ,
};
- if (
- activeElement !== undefined &&
- filterInView?.operator === OPERATOR_IN
- ) {
+ if ( filterInView?.operator === OPERATOR_IN ) {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 2: Filter value. e.g.: "Author is Admin". */
__( '%1$s is %2$s' ),
filter.name,
- activeElement.label
+ activeElements.map( ( element ) => element.label ).join( ', ' )
),
filterTextWrappers
);
}
- if (
- activeElement !== undefined &&
- filterInView?.operator === OPERATOR_NOT_IN
- ) {
+ if ( filterInView?.operator === OPERATOR_NOT_IN ) {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 2: Filter value. e.g.: "Author is not Admin". */
__( '%1$s is not %2$s' ),
filter.name,
- activeElement.label
+ activeElements.map( ( element ) => element.label ).join( ', ' )
),
filterTextWrappers
);
@@ -140,9 +134,12 @@ export default function FilterSummary( {
const toggleRef = useRef();
const { filter, view, onChangeView } = commonProps;
const filterInView = view.filters.find( ( f ) => f.field === filter.field );
- const activeElement = filter.elements.find(
- ( element ) => element.value === filterInView?.value
- );
+ const activeElements = filter.elements.filter( ( element ) => {
+ if ( filter.singleSelection ) {
+ return element.value === filterInView?.value;
+ }
+ return filterInView?.value?.includes( element.value );
+ } );
const isPrimary = filter.isPrimary;
const hasValues = filterInView?.value !== undefined;
const canResetOrRemove = ! isPrimary || hasValues;
@@ -188,7 +185,7 @@ export default function FilterSummary( {
ref={ toggleRef }
>
diff --git a/packages/dataviews/src/filters.js b/packages/dataviews/src/filters.js
index eb1bce3a42dc4..38eafd2e57675 100644
--- a/packages/dataviews/src/filters.js
+++ b/packages/dataviews/src/filters.js
@@ -43,6 +43,7 @@ const Filters = memo( function Filters( {
field: field.id,
name: field.header,
elements: field.elements,
+ singleSelection: !! field.filterBy?.singleSelection,
operators,
isVisible:
isPrimary ||
diff --git a/packages/dataviews/src/search-widget.js b/packages/dataviews/src/search-widget.js
index f8b3e84fd8ba3..7ff0688dab471 100644
--- a/packages/dataviews/src/search-widget.js
+++ b/packages/dataviews/src/search-widget.js
@@ -15,7 +15,7 @@ import {
Icon,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
-import { search } from '@wordpress/icons';
+import { search, check } from '@wordpress/icons';
import { SVG, Circle } from '@wordpress/primitives';
/**
@@ -48,10 +48,25 @@ function ListBox( { view, filter, onChangeView } ) {
// so the first item is not selected, since the focus is on the operators control.
defaultActiveId: filter.operators?.length === 1 ? undefined : null,
} );
- const selectedFilter = view.filters.find(
- ( _filter ) => _filter.field === filter.field
+ const currentFilter = view.filters.find(
+ ( f ) => f.field === filter.field
);
- const selectedValues = selectedFilter?.value;
+ const getSelectedValues = ( current ) => {
+ if ( filter.singleSelection ) {
+ return current?.value;
+ }
+
+ if ( Array.isArray( current?.value ) ) {
+ return current.value;
+ }
+
+ if ( ! Array.isArray( current?.value ) && !! current?.value ) {
+ return [ current.value ];
+ }
+
+ return [];
+ };
+ const selectedValues = getSelectedValues( currentFilter );
return (
}
onClick={ () => {
- const currentFilter = view.filters.find(
- ( _filter ) =>
- _filter.field === filter.field
- );
+ const getNewValue = (
+ filterDefinition,
+ current,
+ value
+ ) => {
+ if ( filterDefinition.singleSelection ) {
+ return value;
+ }
+
+ if ( Array.isArray( current?.value ) ) {
+ return current.value.includes(
+ element.value
+ )
+ ? current.value.filter(
+ ( v ) => v !== value
+ )
+ : [ ...current.value, value ];
+ }
+
+ return [ value ];
+ };
const newFilters = currentFilter
? [
...view.filters.map(
@@ -101,7 +133,11 @@ function ListBox( { view, filter, onChangeView } ) {
currentFilter.operator ||
filter
.operators[ 0 ],
- value: element.value,
+ value: getNewValue(
+ filter,
+ currentFilter,
+ element.value
+ ),
};
}
return _filter;
@@ -113,7 +149,11 @@ function ListBox( { view, filter, onChangeView } ) {
{
field: filter.field,
operator: filter.operators[ 0 ],
- value: element.value,
+ value: getNewValue(
+ filter,
+ currentFilter,
+ element.value
+ ),
},
];
onChangeView( {
@@ -126,9 +166,14 @@ function ListBox( { view, filter, onChangeView } ) {
}
>
- { selectedValues === element.value && (
-
- ) }
+ { filter.singleSelection &&
+ selectedValues === element.value && (
+
+ ) }
+ { ! filter.singleSelection &&
+ selectedValues.includes( element.value ) && (
+
+ ) }
{ element.label }
diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js
index 73dd87eeab5a5..0a5be7bfe5683 100644
--- a/packages/edit-site/src/components/page-pages/index.js
+++ b/packages/edit-site/src/components/page-pages/index.js
@@ -320,6 +320,9 @@ export default function PagePages() {
value: id,
label: name,
} ) ) || [],
+ filterBy: {
+ singleSelection: true,
+ },
},
{
header: __( 'Status' ),
From 0de9f370bed91957d69cf652a4061d09de6d4447 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Tue, 5 Mar 2024 18:23:11 +0100
Subject: [PATCH 02/24] Make Sync filter in pattern single selection and remove
test from Pages
---
packages/edit-site/src/components/page-pages/index.js | 3 ---
packages/edit-site/src/components/page-patterns/index.js | 1 +
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js
index 0a5be7bfe5683..73dd87eeab5a5 100644
--- a/packages/edit-site/src/components/page-pages/index.js
+++ b/packages/edit-site/src/components/page-pages/index.js
@@ -320,9 +320,6 @@ export default function PagePages() {
value: id,
label: name,
} ) ) || [],
- filterBy: {
- singleSelection: true,
- },
},
{
header: __( 'Status' ),
diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js
index 8ca10d2357e55..0ad237b0a10dc 100644
--- a/packages/edit-site/src/components/page-patterns/index.js
+++ b/packages/edit-site/src/components/page-patterns/index.js
@@ -325,6 +325,7 @@ export default function DataviewsPatterns() {
filterBy: {
operators: [ OPERATOR_IN ],
isPrimary: true,
+ singleSelection: true,
},
enableSorting: false,
} );
From 44f305217fcce506087c1a9a6d80669be29cb17b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Tue, 5 Mar 2024 18:56:39 +0100
Subject: [PATCH 03/24] Document singleSelection
---
packages/dataviews/README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index bbc2271db6573..a774fec7f7138 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -108,6 +108,7 @@ Each field is an object with the following properties:
- `filterBy`: configuration for the filters.
- `operators`: the list of operators supported by the field.
- `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter.
+ - `singleSelection`: whether the filter should only allow single selection. `false` by default.
### `view`: `object`
From e6cc4b0a3e1a3fff8c01abec0daf410b9155f831 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 6 Mar 2024 10:18:28 +0100
Subject: [PATCH 04/24] Implement multiple selection for combobox
---
packages/dataviews/src/search-widget.js | 102 ++++++++++++------------
1 file changed, 53 insertions(+), 49 deletions(-)
diff --git a/packages/dataviews/src/search-widget.js b/packages/dataviews/src/search-widget.js
index 7ff0688dab471..ab80bded5bb90 100644
--- a/packages/dataviews/src/search-widget.js
+++ b/packages/dataviews/src/search-widget.js
@@ -39,6 +39,36 @@ function normalizeSearchInput( input = '' ) {
return removeAccents( input.trim().toLowerCase() );
}
+const getCurrentValue = ( filterDefinition, currentFilter ) => {
+ if ( filterDefinition.singleSelection ) {
+ return currentFilter?.value;
+ }
+
+ if ( Array.isArray( currentFilter?.value ) ) {
+ return currentFilter.value;
+ }
+
+ if ( ! Array.isArray( currentFilter?.value ) && !! currentFilter?.value ) {
+ return [ currentFilter.value ];
+ }
+
+ return [];
+};
+
+const getNewValue = ( filterDefinition, currentFilter, value ) => {
+ if ( filterDefinition.singleSelection ) {
+ return value;
+ }
+
+ if ( Array.isArray( currentFilter?.value ) ) {
+ return currentFilter.value.includes( value )
+ ? currentFilter.value.filter( ( v ) => v !== value )
+ : [ ...currentFilter.value, value ];
+ }
+
+ return [ value ];
+};
+
function ListBox( { view, filter, onChangeView } ) {
const compositeStore = useCompositeStore( {
virtualFocus: true,
@@ -51,22 +81,7 @@ function ListBox( { view, filter, onChangeView } ) {
const currentFilter = view.filters.find(
( f ) => f.field === filter.field
);
- const getSelectedValues = ( current ) => {
- if ( filter.singleSelection ) {
- return current?.value;
- }
-
- if ( Array.isArray( current?.value ) ) {
- return current.value;
- }
-
- if ( ! Array.isArray( current?.value ) && !! current?.value ) {
- return [ current.value ];
- }
-
- return [];
- };
- const selectedValues = getSelectedValues( currentFilter );
+ const currentValue = getCurrentValue( filter, currentFilter );
return (
}
onClick={ () => {
- const getNewValue = (
- filterDefinition,
- current,
- value
- ) => {
- if ( filterDefinition.singleSelection ) {
- return value;
- }
-
- if ( Array.isArray( current?.value ) ) {
- return current.value.includes(
- element.value
- )
- ? current.value.filter(
- ( v ) => v !== value
- )
- : [ ...current.value, value ];
- }
-
- return [ value ];
- };
const newFilters = currentFilter
? [
...view.filters.map(
@@ -167,11 +161,11 @@ function ListBox( { view, filter, onChangeView } ) {
>
{ filter.singleSelection &&
- selectedValues === element.value && (
+ currentValue === element.value && (
) }
{ ! filter.singleSelection &&
- selectedValues.includes( element.value ) && (
+ currentValue.includes( element.value ) && (
) }
@@ -192,10 +186,10 @@ function ListBox( { view, filter, onChangeView } ) {
function ComboboxList( { view, filter, onChangeView } ) {
const [ searchValue, setSearchValue ] = useState( '' );
const deferredSearchValue = useDeferredValue( searchValue );
- const selectedFilter = view.filters.find(
+ const currentFilter = view.filters.find(
( _filter ) => _filter.field === filter.field
);
- const selectedValues = selectedFilter?.value;
+ const currentValue = getCurrentValue( filter, currentFilter );
const matches = useMemo( () => {
const normalizedSearch = normalizeSearchInput( deferredSearchValue );
return filter.elements.filter( ( item ) =>
@@ -206,9 +200,6 @@ function ComboboxList( { view, filter, onChangeView } ) {
{
- const currentFilter = view.filters.find(
- ( _filter ) => _filter.field === filter.field
- );
const newFilters = currentFilter
? [
...view.filters.map( ( _filter ) => {
@@ -218,7 +209,11 @@ function ComboboxList( { view, filter, onChangeView } ) {
operator:
currentFilter.operator ||
filter.operators[ 0 ],
- value,
+ value: getNewValue(
+ filter,
+ currentFilter,
+ value
+ ),
};
}
return _filter;
@@ -229,7 +224,11 @@ function ComboboxList( { view, filter, onChangeView } ) {
{
field: filter.field,
operator: filter.operators[ 0 ],
- value,
+ value: getNewValue(
+ filter,
+ currentFilter,
+ value
+ ),
},
];
onChangeView( {
@@ -268,9 +267,14 @@ function ComboboxList( { view, filter, onChangeView } ) {
focusOnHover
>
- { selectedValues === element.value && (
-
- ) }
+ { filter.singleSelection &&
+ currentValue === element.value && (
+
+ ) }
+ { ! filter.singleSelection &&
+ currentValue.includes( element.value ) && (
+
+ ) }
Date: Wed, 6 Mar 2024 10:38:56 +0100
Subject: [PATCH 05/24] Fix Combobox multiselection
---
packages/dataviews/src/search-widget.js | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/packages/dataviews/src/search-widget.js b/packages/dataviews/src/search-widget.js
index ab80bded5bb90..d6fcddf969b60 100644
--- a/packages/dataviews/src/search-widget.js
+++ b/packages/dataviews/src/search-widget.js
@@ -199,6 +199,7 @@ function ComboboxList( { view, filter, onChangeView } ) {
return (
{
const newFilters = currentFilter
? [
@@ -209,11 +210,7 @@ function ComboboxList( { view, filter, onChangeView } ) {
operator:
currentFilter.operator ||
filter.operators[ 0 ],
- value: getNewValue(
- filter,
- currentFilter,
- value
- ),
+ value,
};
}
return _filter;
@@ -224,11 +221,7 @@ function ComboboxList( { view, filter, onChangeView } ) {
{
field: filter.field,
operator: filter.operators[ 0 ],
- value: getNewValue(
- filter,
- currentFilter,
- value
- ),
+ value,
},
];
onChangeView( {
From 7ced562d274a8f4c5d2b39e441cec157590e48da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 6 Mar 2024 16:35:15 +0100
Subject: [PATCH 06/24] Add OPERATOR_EQUAL and OPERATOR_NOT_EQUAL
---
packages/dataviews/README.md | 11 +++++++---
packages/dataviews/src/constants.js | 18 +++++++++++++++-
packages/dataviews/src/filter-summary.js | 21 +++++--------------
packages/dataviews/src/filters.js | 15 ++++++++-----
packages/dataviews/src/utils.js | 6 +++---
.../src/components/page-patterns/index.js | 5 ++---
packages/edit-site/src/utils/constants.js | 2 ++
7 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index a774fec7f7138..2f17802176017 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -108,7 +108,6 @@ Each field is an object with the following properties:
- `filterBy`: configuration for the filters.
- `operators`: the list of operators supported by the field.
- `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter.
- - `singleSelection`: whether the filter should only allow single selection. `false` by default.
### `view`: `object`
@@ -283,8 +282,14 @@ Callback that signals the user triggered the details for one of more items, and
### Operators
-- `in`: operator to be used in filters for fields of type `enumeration`.
-- `notIn`: operator to be used in filters for fields of type `enumeration`.
+Allowed operators for fields of type `enumeration`:
+
+- `equal`: whether an item is equal to a single value.
+- `notEqual`: whether an item is not equal to a single value.
+- `in`: whether an item is in a list of values.
+- `notIn`: whether an item is not in a list of values.
+
+By default, a field of type `enumeration` supports `in` and `notIn` operators — this is, it supports multiselection and negation.
## Contributing to this package
diff --git a/packages/dataviews/src/constants.js b/packages/dataviews/src/constants.js
index b3d17d7fd1145..3d250d784d7e0 100644
--- a/packages/dataviews/src/constants.js
+++ b/packages/dataviews/src/constants.js
@@ -22,13 +22,29 @@ export const ENUMERATION_TYPE = 'enumeration';
// Filter operators.
export const OPERATOR_IN = 'in';
export const OPERATOR_NOT_IN = 'notIn';
+export const OPERATOR_EQUAL = 'equal';
+export const OPERATOR_NOT_EQUAL = 'notEqual';
+export const ALL_OPERATORS = [
+ OPERATOR_IN,
+ OPERATOR_NOT_IN,
+ OPERATOR_EQUAL,
+ OPERATOR_NOT_EQUAL,
+];
export const OPERATORS = {
[ OPERATOR_IN ]: {
key: 'in-filter',
- label: __( 'Is' ),
+ label: __( 'In' ),
},
[ OPERATOR_NOT_IN ]: {
key: 'not-in-filter',
+ label: __( 'Not in' ),
+ },
+ [ OPERATOR_EQUAL ]: {
+ key: 'equal-filter',
+ label: __( 'Is' ),
+ },
+ [ OPERATOR_NOT_EQUAL ]: {
+ key: 'not-equal-filter',
label: __( 'Is not' ),
},
};
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index 67571d7dd9535..b49794598c2b9 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -24,7 +24,7 @@ import { ENTER, SPACE } from '@wordpress/keycodes';
* Internal dependencies
*/
import SearchWidget from './search-widget';
-import { OPERATOR_IN, OPERATOR_NOT_IN, OPERATORS } from './constants';
+import { ALL_OPERATORS, OPERATORS } from './constants';
const FilterText = ( { activeElements, filterInView, filter } ) => {
if ( activeElements === undefined || activeElements.length === 0 ) {
@@ -36,24 +36,13 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
Span2: ,
};
- if ( filterInView?.operator === OPERATOR_IN ) {
+ if ( ALL_OPERATORS.includes( filterInView?.operator ) ) {
return createInterpolateElement(
sprintf(
- /* translators: 1: Filter name. 2: Filter value. e.g.: "Author is Admin". */
- __( '%1$s is %2$s' ),
- filter.name,
- activeElements.map( ( element ) => element.label ).join( ', ' )
- ),
- filterTextWrappers
- );
- }
-
- if ( filterInView?.operator === OPERATOR_NOT_IN ) {
- return createInterpolateElement(
- sprintf(
- /* translators: 1: Filter name. 2: Filter value. e.g.: "Author is not Admin". */
- __( '%1$s is not %2$s' ),
+ /* translators: 1: Filter name. 2: Filter operator. 3: Filter value. e.g.: "Author is Admin", "Author not in Admin, Editor". */
+ __( '%1$s %2$s %3$s' ),
filter.name,
+ OPERATORS[ filterInView.operator ].label,
activeElements.map( ( element ) => element.label ).join( ', ' )
),
filterTextWrappers
diff --git a/packages/dataviews/src/filters.js b/packages/dataviews/src/filters.js
index 38eafd2e57675..df70568333db1 100644
--- a/packages/dataviews/src/filters.js
+++ b/packages/dataviews/src/filters.js
@@ -10,7 +10,12 @@ import FilterSummary from './filter-summary';
import AddFilter from './add-filter';
import ResetFilters from './reset-filters';
import { sanitizeOperators } from './utils';
-import { ENUMERATION_TYPE, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
+import {
+ ENUMERATION_TYPE,
+ ALL_OPERATORS,
+ OPERATOR_EQUAL,
+ OPERATOR_NOT_EQUAL,
+} from './constants';
import { __experimentalHStack as HStack } from '@wordpress/components';
const Filters = memo( function Filters( {
@@ -43,16 +48,16 @@ const Filters = memo( function Filters( {
field: field.id,
name: field.header,
elements: field.elements,
- singleSelection: !! field.filterBy?.singleSelection,
+ singleSelection: operators.some( ( op ) =>
+ [ OPERATOR_EQUAL, OPERATOR_NOT_EQUAL ].includes( op )
+ ),
operators,
isVisible:
isPrimary ||
view.filters.some(
( f ) =>
f.field === field.id &&
- [ OPERATOR_IN, OPERATOR_NOT_IN ].includes(
- f.operator
- )
+ ALL_OPERATORS.includes( f.operator )
),
isPrimary,
} );
diff --git a/packages/dataviews/src/utils.js b/packages/dataviews/src/utils.js
index 99fd56acc4280..f6c987630d87d 100644
--- a/packages/dataviews/src/utils.js
+++ b/packages/dataviews/src/utils.js
@@ -7,7 +7,7 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components';
/**
* Internal dependencies
*/
-import { OPERATORS } from './constants';
+import { ALL_OPERATORS, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
import { unlock } from './lock-unlock';
const { DropdownMenuSeparatorV2: DropdownMenuSeparator } = unlock(
@@ -69,10 +69,10 @@ export function getPaginationResults( { data, view } ) {
export const sanitizeOperators = ( field ) => {
let operators = field.filterBy?.operators;
if ( ! operators || ! Array.isArray( operators ) ) {
- operators = Object.keys( OPERATORS );
+ operators = [ OPERATOR_IN, OPERATOR_NOT_IN ]; // Default values.
}
return operators.filter( ( operator ) =>
- Object.keys( OPERATORS ).includes( operator )
+ ALL_OPERATORS.includes( operator )
);
};
diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js
index 0ad237b0a10dc..e7de0cc2af68f 100644
--- a/packages/edit-site/src/components/page-patterns/index.js
+++ b/packages/edit-site/src/components/page-patterns/index.js
@@ -46,7 +46,7 @@ import {
PATTERN_SYNC_TYPES,
PATTERN_DEFAULT_CATEGORY,
ENUMERATION_TYPE,
- OPERATOR_IN,
+ OPERATOR_EQUAL,
} from '../../utils/constants';
import {
exportJSONaction,
@@ -323,9 +323,8 @@ export default function DataviewsPatterns() {
type: ENUMERATION_TYPE,
elements: SYNC_FILTERS,
filterBy: {
- operators: [ OPERATOR_IN ],
+ operators: [ OPERATOR_EQUAL ],
isPrimary: true,
- singleSelection: true,
},
enableSorting: false,
} );
diff --git a/packages/edit-site/src/utils/constants.js b/packages/edit-site/src/utils/constants.js
index f5ca89b9fb62c..7aa0d1dd129ee 100644
--- a/packages/edit-site/src/utils/constants.js
+++ b/packages/edit-site/src/utils/constants.js
@@ -52,3 +52,5 @@ export const LAYOUT_LIST = 'list';
export const ENUMERATION_TYPE = 'enumeration';
export const OPERATOR_IN = 'in';
export const OPERATOR_NOT_IN = 'notIn';
+export const OPERATOR_EQUAL = 'equal';
+export const OPERATOR_NOT_EQUAL = 'notEqual';
From e9ae3628d34339b64b30545baed0885305685acd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 6 Mar 2024 16:42:45 +0100
Subject: [PATCH 07/24] Sanitize operators: do not allow mixing single&multi
selection
---
packages/dataviews/src/utils.js | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/packages/dataviews/src/utils.js b/packages/dataviews/src/utils.js
index f6c987630d87d..445fd1960b43f 100644
--- a/packages/dataviews/src/utils.js
+++ b/packages/dataviews/src/utils.js
@@ -68,12 +68,28 @@ export function getPaginationResults( { data, view } ) {
export const sanitizeOperators = ( field ) => {
let operators = field.filterBy?.operators;
+
+ // Assign default values.
if ( ! operators || ! Array.isArray( operators ) ) {
- operators = [ OPERATOR_IN, OPERATOR_NOT_IN ]; // Default values.
+ operators = [ OPERATOR_IN, OPERATOR_NOT_IN ];
}
- return operators.filter( ( operator ) =>
+
+ // Make sure only valid operators are used.
+ operators = operators.filter( ( operator ) =>
ALL_OPERATORS.includes( operator )
);
+
+ // Do not allow mixing single & multiselection operators.
+ if (
+ operators.includes( OPERATOR_IN ) ||
+ operators.includes( OPERATOR_NOT_IN )
+ ) {
+ operators = operators.filter( ( operator ) =>
+ [ OPERATOR_IN, OPERATOR_NOT_IN ].includes( operator )
+ );
+ }
+
+ return operators;
};
export function WithDropDownMenuSeparators( { children } ) {
From 5608a772190c5ef5d9efd7f3d87fd172ee8ec027 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 6 Mar 2024 16:46:03 +0100
Subject: [PATCH 08/24] Update docs
---
packages/dataviews/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index 2f17802176017..4dd2391601715 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -289,7 +289,7 @@ Allowed operators for fields of type `enumeration`:
- `in`: whether an item is in a list of values.
- `notIn`: whether an item is not in a list of values.
-By default, a field of type `enumeration` supports `in` and `notIn` operators — this is, it supports multiselection and negation.
+By default, a field of type `enumeration` supports `in` and `notIn` operators — this is, it supports multiselection and negation. A filter cannot mix single-selection (`equal`, `notEqual`) & multi-selection (`in`, `notIn`) operators. If a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded.
## Contributing to this package
From 46da75df80ca656d0f33c26f8c8167a3a68a7773 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Wed, 6 Mar 2024 16:47:28 +0100
Subject: [PATCH 09/24] Update docs
---
packages/dataviews/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index 4dd2391601715..6ce5451cd91dc 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -289,7 +289,7 @@ Allowed operators for fields of type `enumeration`:
- `in`: whether an item is in a list of values.
- `notIn`: whether an item is not in a list of values.
-By default, a field of type `enumeration` supports `in` and `notIn` operators — this is, it supports multiselection and negation. A filter cannot mix single-selection (`equal`, `notEqual`) & multi-selection (`in`, `notIn`) operators. If a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded.
+By default, a field of type `enumeration` supports `in` and `notIn` operators — this is, it supports multiselection and negation. A filter cannot mix single-selection (`equal`, `notEqual`) & multi-selection (`in`, `notIn`) operators. If a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item.
## Contributing to this package
From b9237bdbae88eef23dfaec4d02f7b40284c38ba7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 09:15:46 +0100
Subject: [PATCH 10/24] Fix translations
---
packages/dataviews/src/filter-summary.js | 51 +++++++++++++++++++++---
1 file changed, 46 insertions(+), 5 deletions(-)
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index b49794598c2b9..1c9a289bce5c9 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -24,7 +24,13 @@ import { ENTER, SPACE } from '@wordpress/keycodes';
* Internal dependencies
*/
import SearchWidget from './search-widget';
-import { ALL_OPERATORS, OPERATORS } from './constants';
+import {
+ OPERATORS,
+ OPERATOR_IN,
+ OPERATOR_NOT_IN,
+ OPERATOR_EQUAL,
+ OPERATOR_NOT_EQUAL,
+} from './constants';
const FilterText = ( { activeElements, filterInView, filter } ) => {
if ( activeElements === undefined || activeElements.length === 0 ) {
@@ -36,19 +42,54 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
Span2: ,
};
- if ( ALL_OPERATORS.includes( filterInView?.operator ) ) {
+ if ( filterInView?.operator === OPERATOR_IN ) {
return createInterpolateElement(
sprintf(
- /* translators: 1: Filter name. 2: Filter operator. 3: Filter value. e.g.: "Author is Admin", "Author not in Admin, Editor". */
- __( '%1$s %2$s %3$s' ),
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author in Admin, Editor". */
+ __( '%1$s in %2$s' ),
filter.name,
- OPERATORS[ filterInView.operator ].label,
activeElements.map( ( element ) => element.label ).join( ', ' )
),
filterTextWrappers
);
}
+ if ( filterInView?.operator === OPERATOR_NOT_IN ) {
+ return createInterpolateElement(
+ sprintf(
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author not in Admin, Editor". */
+ __( '%1$s not in %2$s' ),
+ filter.name,
+ activeElements.map( ( element ) => element.label ).join( ', ' )
+ ),
+ filterTextWrappers
+ );
+ }
+
+ if ( filterInView?.operator === OPERATOR_EQUAL ) {
+ return createInterpolateElement(
+ sprintf(
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is Admin". */
+ __( '%1$s is %2$s' ),
+ filter.name,
+ activeElements[ 0 ].label
+ ),
+ filterTextWrappers
+ );
+ }
+
+ if ( filterInView?.operator === OPERATOR_NOT_EQUAL ) {
+ return createInterpolateElement(
+ sprintf(
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is not Admin". */
+ __( '%1$s is not %2$s' ),
+ filter.name,
+ activeElements[ 0 ].label
+ ),
+ filterTextWrappers
+ );
+ }
+
return sprintf(
/* translators: 1: Filter name e.g.: "Unknown status for Author". */
__( 'Unknown status for %1$s' ),
From c432000b754f02c3650218eb64ad4499a5eacc8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 10:35:50 +0100
Subject: [PATCH 11/24] Add changelog note
---
packages/dataviews/CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index 80579710332ba..d0b5cdb14ead6 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `is` and `isNot` for single-selection filters.
+
## 0.7.0 (2024-03-06)
## 0.6.0 (2024-02-21)
From 91b285ac356c0a3c83c41c049850b544685d04d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 10:51:10 +0100
Subject: [PATCH 12/24] Truncate text
---
packages/dataviews/src/filter-summary.js | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index 1c9a289bce5c9..f81a1bf5964d7 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -14,6 +14,7 @@ import {
SelectControl,
Tooltip,
Icon,
+ __experimentalTruncate as Truncate,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { useRef, createInterpolateElement } from '@wordpress/element';
@@ -38,15 +39,21 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
}
const filterTextWrappers = {
- Span1: ,
- Span2: ,
+ Name: ,
+ Value: (
+
+ ),
};
if ( filterInView?.operator === OPERATOR_IN ) {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 3: Filter value. e.g.: "Author in Admin, Editor". */
- __( '%1$s in %2$s' ),
+ __( '%1$s in %2$s' ),
filter.name,
activeElements.map( ( element ) => element.label ).join( ', ' )
),
@@ -58,7 +65,7 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 3: Filter value. e.g.: "Author not in Admin, Editor". */
- __( '%1$s not in %2$s' ),
+ __( '%1$s not in %2$s' ),
filter.name,
activeElements.map( ( element ) => element.label ).join( ', ' )
),
@@ -70,7 +77,7 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 3: Filter value. e.g.: "Author is Admin". */
- __( '%1$s is %2$s' ),
+ __( '%1$s is %2$s' ),
filter.name,
activeElements[ 0 ].label
),
@@ -82,7 +89,7 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 3: Filter value. e.g.: "Author is not Admin". */
- __( '%1$s is not %2$s' ),
+ __( '%1$s is not %2$s' ),
filter.name,
activeElements[ 0 ].label
),
From d5a4d86f150ed387e1de8d1f6b0d4688653cdc07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 12:26:56 +0100
Subject: [PATCH 13/24] Fix templates & template parts custom filters
---
.../src/components/page-templates-template-parts/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/edit-site/src/components/page-templates-template-parts/index.js b/packages/edit-site/src/components/page-templates-template-parts/index.js
index 1462c5143a71d..6410f3498283f 100644
--- a/packages/edit-site/src/components/page-templates-template-parts/index.js
+++ b/packages/edit-site/src/components/page-templates-template-parts/index.js
@@ -382,7 +382,7 @@ export default function PageTemplatesTemplateParts( { postType } ) {
!! filter.value
) {
filteredData = filteredData.filter( ( item ) => {
- return item.author_text === filter.value;
+ return filter.value.includes( item.author_text );
} );
} else if (
filter.field === 'author' &&
@@ -390,7 +390,7 @@ export default function PageTemplatesTemplateParts( { postType } ) {
!! filter.value
) {
filteredData = filteredData.filter( ( item ) => {
- return item.author_text !== filter.value;
+ return ! filter.value.includes( item.author_text );
} );
}
} );
From c273beac33da46c9d81fd20e36618d03641c70d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 12:27:57 +0100
Subject: [PATCH 14/24] Update changelog
---
packages/dataviews/CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index d0b5cdb14ead6..fc685fa28ce37 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -2,7 +2,7 @@
## Unreleased
-- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `is` and `isNot` for single-selection filters.
+- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `is` and `isNot` for single-selection filters. Note that the value in the `filters.view` would be an array for multi-selection as well.
## 0.7.0 (2024-03-06)
From 54ef1653170c454a9a87ddb99f29c40511b22924 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 12:49:49 +0100
Subject: [PATCH 15/24] Update terms
---
packages/dataviews/CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index fc685fa28ce37..498fdbbd00017 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -2,7 +2,7 @@
## Unreleased
-- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `is` and `isNot` for single-selection filters. Note that the value in the `filters.view` would be an array for multi-selection as well.
+- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `equal` and `notEqual` for single-selection filters. Note that the value in the `filters.view` would be an array for multi-selection as well.
## 0.7.0 (2024-03-06)
From 532d4eeaab197715bca90ac1f5d405c4ddfbe692 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 14:09:28 +0100
Subject: [PATCH 16/24] Fix summary tag
---
packages/dataviews/src/filter-summary.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index f81a1bf5964d7..9f6289bb6c4f1 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -89,7 +89,7 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
return createInterpolateElement(
sprintf(
/* translators: 1: Filter name. 3: Filter value. e.g.: "Author is not Admin". */
- __( '%1$s is not %2$s' ),
+ __( '%1$s is not %2$s' ),
filter.name,
activeElements[ 0 ].label
),
From 2ad3269c649e1638ead78a23b3512033137bceaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 14:14:34 +0100
Subject: [PATCH 17/24] Sanitize operators: remove multiselection ops if any
single op is present
---
packages/dataviews/src/utils.js | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/packages/dataviews/src/utils.js b/packages/dataviews/src/utils.js
index 445fd1960b43f..7440cc46bff5f 100644
--- a/packages/dataviews/src/utils.js
+++ b/packages/dataviews/src/utils.js
@@ -7,7 +7,13 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components';
/**
* Internal dependencies
*/
-import { ALL_OPERATORS, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
+import {
+ ALL_OPERATORS,
+ OPERATOR_EQUAL,
+ OPERATOR_IN,
+ OPERATOR_NOT_EQUAL,
+ OPERATOR_NOT_IN,
+} from './constants';
import { unlock } from './lock-unlock';
const { DropdownMenuSeparatorV2: DropdownMenuSeparator } = unlock(
@@ -80,12 +86,13 @@ export const sanitizeOperators = ( field ) => {
);
// Do not allow mixing single & multiselection operators.
+ // Remove multiselction operators if any of the single selection ones is present.
if (
- operators.includes( OPERATOR_IN ) ||
- operators.includes( OPERATOR_NOT_IN )
+ operators.includes( OPERATOR_EQUAL ) ||
+ operators.includes( OPERATOR_NOT_EQUAL )
) {
operators = operators.filter( ( operator ) =>
- [ OPERATOR_IN, OPERATOR_NOT_IN ].includes( operator )
+ [ OPERATOR_EQUAL, OPERATOR_NOT_EQUAL ].includes( operator )
);
}
From 8f36ce37d8164486bfe24d561136e894dc39f5f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 17:46:14 +0100
Subject: [PATCH 18/24] Remove truncation
---
packages/dataviews/src/filter-summary.js | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index 9f6289bb6c4f1..07dcdf9cf873e 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -14,7 +14,6 @@ import {
SelectControl,
Tooltip,
Icon,
- __experimentalTruncate as Truncate,
} from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { useRef, createInterpolateElement } from '@wordpress/element';
@@ -40,13 +39,7 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
const filterTextWrappers = {
Name: ,
- Value: (
-
- ),
+ Value: ,
};
if ( filterInView?.operator === OPERATOR_IN ) {
From 5f3450c0bca3883154fba4f0b2cdaced20c27e6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Thu, 7 Mar 2024 17:49:40 +0100
Subject: [PATCH 19/24] Fix template & parts custom filtering
---
.../src/components/page-templates-template-parts/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/edit-site/src/components/page-templates-template-parts/index.js b/packages/edit-site/src/components/page-templates-template-parts/index.js
index 6410f3498283f..08742bd724c3d 100644
--- a/packages/edit-site/src/components/page-templates-template-parts/index.js
+++ b/packages/edit-site/src/components/page-templates-template-parts/index.js
@@ -379,7 +379,7 @@ export default function PageTemplatesTemplateParts( { postType } ) {
if (
filter.field === 'author' &&
filter.operator === OPERATOR_IN &&
- !! filter.value
+ filter?.value?.length > 0
) {
filteredData = filteredData.filter( ( item ) => {
return filter.value.includes( item.author_text );
@@ -387,7 +387,7 @@ export default function PageTemplatesTemplateParts( { postType } ) {
} else if (
filter.field === 'author' &&
filter.operator === OPERATOR_NOT_IN &&
- !! filter.value
+ filter?.value?.length > 0
) {
filteredData = filteredData.filter( ( item ) => {
return ! filter.value.includes( item.author_text );
From 374ad5aeed4f549cded8b298a2d781666061e244 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Mon, 11 Mar 2024 14:16:23 +0100
Subject: [PATCH 20/24] Tag the changes as Enhancements
---
packages/dataviews/CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index 498fdbbd00017..3451617a069ea 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+### Enhancement
+
- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `equal` and `notEqual` for single-selection filters. Note that the value in the `filters.view` would be an array for multi-selection as well.
## 0.7.0 (2024-03-06)
From bee646ac019425bf1935564fc1cbd3376cf68f5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Mon, 11 Mar 2024 14:32:38 +0100
Subject: [PATCH 21/24] DataViews: use is/isNot/isAny/isNone operators
---
packages/dataviews/src/constants.js | 40 ++++++++++++------------
packages/dataviews/src/filter-summary.js | 32 +++++++++----------
packages/dataviews/src/filters.js | 6 ++--
packages/dataviews/src/utils.js | 29 +++++++++++------
4 files changed, 59 insertions(+), 48 deletions(-)
diff --git a/packages/dataviews/src/constants.js b/packages/dataviews/src/constants.js
index 3d250d784d7e0..bae974ff4eb5f 100644
--- a/packages/dataviews/src/constants.js
+++ b/packages/dataviews/src/constants.js
@@ -20,33 +20,33 @@ import ViewList from './view-list';
export const ENUMERATION_TYPE = 'enumeration';
// Filter operators.
-export const OPERATOR_IN = 'in';
-export const OPERATOR_NOT_IN = 'notIn';
-export const OPERATOR_EQUAL = 'equal';
-export const OPERATOR_NOT_EQUAL = 'notEqual';
+export const OPERATOR_IS = 'is';
+export const OPERATOR_IS_NOT = 'isNot';
+export const OPERATOR_IS_ANY = 'isAny';
+export const OPERATOR_IS_NONE = 'isNone';
export const ALL_OPERATORS = [
- OPERATOR_IN,
- OPERATOR_NOT_IN,
- OPERATOR_EQUAL,
- OPERATOR_NOT_EQUAL,
+ OPERATOR_IS,
+ OPERATOR_IS_NOT,
+ OPERATOR_IS_ANY,
+ OPERATOR_IS_NONE,
];
export const OPERATORS = {
- [ OPERATOR_IN ]: {
- key: 'in-filter',
- label: __( 'In' ),
- },
- [ OPERATOR_NOT_IN ]: {
- key: 'not-in-filter',
- label: __( 'Not in' ),
- },
- [ OPERATOR_EQUAL ]: {
- key: 'equal-filter',
+ [ OPERATOR_IS ]: {
+ key: 'is-filter',
label: __( 'Is' ),
},
- [ OPERATOR_NOT_EQUAL ]: {
- key: 'not-equal-filter',
+ [ OPERATOR_IS_NOT ]: {
+ key: 'is-not-filter',
label: __( 'Is not' ),
},
+ [ OPERATOR_IS_ANY ]: {
+ key: 'is-any-filter',
+ label: __( 'Is any' ),
+ },
+ [ OPERATOR_IS_NONE ]: {
+ key: 'is-none-filter',
+ label: __( 'Is none' ),
+ },
};
// Sorting
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index 07dcdf9cf873e..f382efb32be80 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -26,10 +26,10 @@ import { ENTER, SPACE } from '@wordpress/keycodes';
import SearchWidget from './search-widget';
import {
OPERATORS,
- OPERATOR_IN,
- OPERATOR_NOT_IN,
- OPERATOR_EQUAL,
- OPERATOR_NOT_EQUAL,
+ OPERATOR_IS,
+ OPERATOR_IS_NOT,
+ OPERATOR_IS_ANY,
+ OPERATOR_IS_NONE,
} from './constants';
const FilterText = ( { activeElements, filterInView, filter } ) => {
@@ -42,11 +42,11 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
Value: ,
};
- if ( filterInView?.operator === OPERATOR_IN ) {
+ if ( filterInView?.operator === OPERATOR_IS_ANY ) {
return createInterpolateElement(
sprintf(
- /* translators: 1: Filter name. 3: Filter value. e.g.: "Author in Admin, Editor". */
- __( '%1$s in %2$s' ),
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is any: Admin, Editor". */
+ __( '%1$s is any: %2$s' ),
filter.name,
activeElements.map( ( element ) => element.label ).join( ', ' )
),
@@ -54,11 +54,11 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
);
}
- if ( filterInView?.operator === OPERATOR_NOT_IN ) {
+ if ( filterInView?.operator === OPERATOR_IS_NONE ) {
return createInterpolateElement(
sprintf(
- /* translators: 1: Filter name. 3: Filter value. e.g.: "Author not in Admin, Editor". */
- __( '%1$s not in %2$s' ),
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is none: Admin, Editor". */
+ __( '%1$s is none: %2$s' ),
filter.name,
activeElements.map( ( element ) => element.label ).join( ', ' )
),
@@ -66,11 +66,11 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
);
}
- if ( filterInView?.operator === OPERATOR_EQUAL ) {
+ if ( filterInView?.operator === OPERATOR_IS ) {
return createInterpolateElement(
sprintf(
- /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is Admin". */
- __( '%1$s is %2$s' ),
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is: Admin". */
+ __( '%1$s is: %2$s' ),
filter.name,
activeElements[ 0 ].label
),
@@ -78,11 +78,11 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
);
}
- if ( filterInView?.operator === OPERATOR_NOT_EQUAL ) {
+ if ( filterInView?.operator === OPERATOR_IS_NOT ) {
return createInterpolateElement(
sprintf(
- /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is not Admin". */
- __( '%1$s is not %2$s' ),
+ /* translators: 1: Filter name. 3: Filter value. e.g.: "Author is not: Admin". */
+ __( '%1$s is not: %2$s' ),
filter.name,
activeElements[ 0 ].label
),
diff --git a/packages/dataviews/src/filters.js b/packages/dataviews/src/filters.js
index df70568333db1..9f5cb0aedf7d8 100644
--- a/packages/dataviews/src/filters.js
+++ b/packages/dataviews/src/filters.js
@@ -13,8 +13,8 @@ import { sanitizeOperators } from './utils';
import {
ENUMERATION_TYPE,
ALL_OPERATORS,
- OPERATOR_EQUAL,
- OPERATOR_NOT_EQUAL,
+ OPERATOR_IS,
+ OPERATOR_IS_NOT,
} from './constants';
import { __experimentalHStack as HStack } from '@wordpress/components';
@@ -49,7 +49,7 @@ const Filters = memo( function Filters( {
name: field.header,
elements: field.elements,
singleSelection: operators.some( ( op ) =>
- [ OPERATOR_EQUAL, OPERATOR_NOT_EQUAL ].includes( op )
+ [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( op )
),
operators,
isVisible:
diff --git a/packages/dataviews/src/utils.js b/packages/dataviews/src/utils.js
index 7440cc46bff5f..3b7a193f3d1e0 100644
--- a/packages/dataviews/src/utils.js
+++ b/packages/dataviews/src/utils.js
@@ -9,10 +9,10 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components';
*/
import {
ALL_OPERATORS,
- OPERATOR_EQUAL,
- OPERATOR_IN,
- OPERATOR_NOT_EQUAL,
- OPERATOR_NOT_IN,
+ OPERATOR_IS,
+ OPERATOR_IS_NOT,
+ OPERATOR_IS_ANY,
+ OPERATOR_IS_NONE,
} from './constants';
import { unlock } from './lock-unlock';
@@ -77,7 +77,18 @@ export const sanitizeOperators = ( field ) => {
// Assign default values.
if ( ! operators || ! Array.isArray( operators ) ) {
- operators = [ OPERATOR_IN, OPERATOR_NOT_IN ];
+ operators = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ];
+ }
+
+ // Transform legacy in, notIn operators to is, isNot.
+ // To be removed in the future.
+ if ( operators.includes( 'in' ) ) {
+ operators = operators.filter( ( operator ) => operator !== 'is' );
+ operators.push( 'is' );
+ }
+ if ( operators.includes( 'notIn' ) ) {
+ operators = operators.filter( ( operator ) => operator !== 'notIn' );
+ operators.push( 'isNot' );
}
// Make sure only valid operators are used.
@@ -86,13 +97,13 @@ export const sanitizeOperators = ( field ) => {
);
// Do not allow mixing single & multiselection operators.
- // Remove multiselction operators if any of the single selection ones is present.
+ // Remove multiselection operators if any of the single selection ones is present.
if (
- operators.includes( OPERATOR_EQUAL ) ||
- operators.includes( OPERATOR_NOT_EQUAL )
+ operators.includes( OPERATOR_IS ) ||
+ operators.includes( OPERATOR_IS_NOT )
) {
operators = operators.filter( ( operator ) =>
- [ OPERATOR_EQUAL, OPERATOR_NOT_EQUAL ].includes( operator )
+ [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( operator )
);
}
From aa6aec083b70ae9aaeabe7e194874b337ffb2440 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Mon, 11 Mar 2024 14:36:12 +0100
Subject: [PATCH 22/24] Site editor: use is/isNot/isAny/isNone operators
---
.../edit-site/src/components/page-pages/index.js | 12 ++++++------
.../page-templates-template-parts/index.js | 8 ++++----
.../components/sidebar-dataviews/default-views.js | 14 +++++++++++---
packages/edit-site/src/utils/constants.js | 8 ++++----
4 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js
index 73dd87eeab5a5..b8b735c4bfcf6 100644
--- a/packages/edit-site/src/components/page-pages/index.js
+++ b/packages/edit-site/src/components/page-pages/index.js
@@ -30,8 +30,8 @@ import {
LAYOUT_GRID,
LAYOUT_TABLE,
LAYOUT_LIST,
- OPERATOR_IN,
- OPERATOR_NOT_IN,
+ OPERATOR_IS_ANY,
+ OPERATOR_IS_NONE,
} from '../../utils/constants';
import {
@@ -222,18 +222,18 @@ export default function PagePages() {
view.filters.forEach( ( filter ) => {
if (
filter.field === 'status' &&
- filter.operator === OPERATOR_IN
+ filter.operator === OPERATOR_IS_ANY
) {
filters.status = filter.value;
}
if (
filter.field === 'author' &&
- filter.operator === OPERATOR_IN
+ filter.operator === OPERATOR_IS_ANY
) {
filters.author = filter.value;
} else if (
filter.field === 'author' &&
- filter.operator === OPERATOR_NOT_IN
+ filter.operator === OPERATOR_IS_NONE
) {
filters.author_exclude = filter.value;
}
@@ -331,7 +331,7 @@ export default function PagePages() {
elements: STATUSES,
enableSorting: false,
filterBy: {
- operators: [ OPERATOR_IN ],
+ operators: [ OPERATOR_IS_ANY ],
},
},
{
diff --git a/packages/edit-site/src/components/page-templates-template-parts/index.js b/packages/edit-site/src/components/page-templates-template-parts/index.js
index 08742bd724c3d..4ada7f98391d6 100644
--- a/packages/edit-site/src/components/page-templates-template-parts/index.js
+++ b/packages/edit-site/src/components/page-templates-template-parts/index.js
@@ -40,8 +40,8 @@ import {
TEMPLATE_POST_TYPE,
TEMPLATE_PART_POST_TYPE,
ENUMERATION_TYPE,
- OPERATOR_IN,
- OPERATOR_NOT_IN,
+ OPERATOR_IS_ANY,
+ OPERATOR_IS_NONE,
LAYOUT_GRID,
LAYOUT_TABLE,
LAYOUT_LIST,
@@ -378,7 +378,7 @@ export default function PageTemplatesTemplateParts( { postType } ) {
view.filters.forEach( ( filter ) => {
if (
filter.field === 'author' &&
- filter.operator === OPERATOR_IN &&
+ filter.operator === OPERATOR_IS_ANY &&
filter?.value?.length > 0
) {
filteredData = filteredData.filter( ( item ) => {
@@ -386,7 +386,7 @@ export default function PageTemplatesTemplateParts( { postType } ) {
} );
} else if (
filter.field === 'author' &&
- filter.operator === OPERATOR_NOT_IN &&
+ filter.operator === OPERATOR_IS_NONE &&
filter?.value?.length > 0
) {
filteredData = filteredData.filter( ( item ) => {
diff --git a/packages/edit-site/src/components/sidebar-dataviews/default-views.js b/packages/edit-site/src/components/sidebar-dataviews/default-views.js
index 1320564d0d9af..c02869dbc7fdb 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/default-views.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/default-views.js
@@ -11,7 +11,7 @@ import {
LAYOUT_LIST,
LAYOUT_TABLE,
LAYOUT_GRID,
- OPERATOR_IN,
+ OPERATOR_IS_ANY,
} from '../../utils/constants';
export const DEFAULT_CONFIG_PER_VIEW_TYPE = {
@@ -61,7 +61,11 @@ export const DEFAULT_VIEWS = {
view: {
...DEFAULT_PAGE_BASE,
filters: [
- { field: 'status', operator: OPERATOR_IN, value: 'draft' },
+ {
+ field: 'status',
+ operator: OPERATOR_IS_ANY,
+ value: 'draft',
+ },
],
},
},
@@ -72,7 +76,11 @@ export const DEFAULT_VIEWS = {
view: {
...DEFAULT_PAGE_BASE,
filters: [
- { field: 'status', operator: OPERATOR_IN, value: 'trash' },
+ {
+ field: 'status',
+ operator: OPERATOR_IS_ANY,
+ value: 'trash',
+ },
],
},
},
diff --git a/packages/edit-site/src/utils/constants.js b/packages/edit-site/src/utils/constants.js
index 7aa0d1dd129ee..dfae1102df921 100644
--- a/packages/edit-site/src/utils/constants.js
+++ b/packages/edit-site/src/utils/constants.js
@@ -50,7 +50,7 @@ export const LAYOUT_GRID = 'grid';
export const LAYOUT_TABLE = 'table';
export const LAYOUT_LIST = 'list';
export const ENUMERATION_TYPE = 'enumeration';
-export const OPERATOR_IN = 'in';
-export const OPERATOR_NOT_IN = 'notIn';
-export const OPERATOR_EQUAL = 'equal';
-export const OPERATOR_NOT_EQUAL = 'notEqual';
+export const OPERATOR_IS = 'is';
+export const OPERATOR_IS_NOT = 'isNot';
+export const OPERATOR_IS_ANY = 'isAny';
+export const OPERATOR_IS_NONE = 'isNone';
From 7e109e1c24435ec079304af6ed8efc43c42c982b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Mon, 11 Mar 2024 14:41:26 +0100
Subject: [PATCH 23/24] fixup
---
packages/edit-site/src/components/page-patterns/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js
index e7de0cc2af68f..d4cedaf27dee5 100644
--- a/packages/edit-site/src/components/page-patterns/index.js
+++ b/packages/edit-site/src/components/page-patterns/index.js
@@ -46,7 +46,7 @@ import {
PATTERN_SYNC_TYPES,
PATTERN_DEFAULT_CATEGORY,
ENUMERATION_TYPE,
- OPERATOR_EQUAL,
+ OPERATOR_IS,
} from '../../utils/constants';
import {
exportJSONaction,
@@ -323,7 +323,7 @@ export default function DataviewsPatterns() {
type: ENUMERATION_TYPE,
elements: SYNC_FILTERS,
filterBy: {
- operators: [ OPERATOR_EQUAL ],
+ operators: [ OPERATOR_IS ],
isPrimary: true,
},
enableSorting: false,
From 70e40ed995c72c2416f915a6f507086caba628cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Maneiro?=
<583546+oandregal@users.noreply.github.com>
Date: Mon, 11 Mar 2024 16:36:24 +0100
Subject: [PATCH 24/24] Update changelog and docs
---
packages/dataviews/CHANGELOG.md | 2 +-
packages/dataviews/README.md | 54 ++++++++++++++++++++++++---------
2 files changed, 41 insertions(+), 15 deletions(-)
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index 3451617a069ea..7c499b16c7501 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -4,7 +4,7 @@
### Enhancement
-- The filters that use `in` and `notIn` operators (the values by default) support now multi-selection. Use `equal` and `notEqual` for single-selection filters. Note that the value in the `filters.view` would be an array for multi-selection as well.
+- DataViews now supports multi-selection. A new set of filter operators has been introduced: `is`, `isNot`, `isAny`, `isNone`. Single-selection operators are `is` and `isNot`, and multi-selection operators are `isAny` and `isNone`. If no operators are declared for a filter, it will support multi-selection. Additionally, the old filter operators `in` and `notIn` operators have been deprecated and will work as `is` and `isNot` respectively. Please, migrate to the new operators as they'll be removed soon.
## 0.7.0 (2024-03-06)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index 6ce5451cd91dc..32135bc65eba2 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -59,6 +59,14 @@ The fields describe the visible items for each record in the dataset.
Example:
```js
+const STATUSES = [
+ { value: 'draft', label: __( 'Draft' ) },
+ { value: 'future', label: __( 'Scheduled' ) },
+ { value: 'pending', label: __( 'Pending Review' ) },
+ { value: 'private', label: __( 'Private' ) },
+ { value: 'publish', label: __( 'Published' ) },
+ { value: 'trash', label: __( 'Trash' ) },
+];
const fields = [
{
id: 'title',
@@ -89,9 +97,25 @@ const fields = [
elements: [
{ value: 1, label: 'Admin' }
{ value: 2, label: 'User' }
- ]
+ ],
+ filterBy: {
+ operators: [ 'is', 'isNot' ]
+ },
enableSorting: false
- }
+ },
+ {
+ header: __( 'Status' ),
+ id: 'status',
+ getValue: ( { item } ) =>
+ STATUSES.find( ( { value } ) => value === item.status )
+ ?.label ?? item.status,
+ type: 'enumeration',
+ elements: STATUSES,
+ filterBy: {
+ operators: [ 'isAny' ],
+ },
+ enableSorting: false,
+ },
]
```
@@ -120,8 +144,8 @@ const view = {
type: 'table',
search: '',
filters: [
- { field: 'author', operator: 'in', value: 2 },
- { field: 'status', operator: 'in', value: 'publish,draft' }
+ { field: 'author', operator: 'is', value: 2 },
+ { field: 'status', operator: 'isAny', value: [ 'publish', 'draft'] }
],
page: 1,
perPage: 5,
@@ -140,7 +164,7 @@ Properties:
- `search`: the text search applied to the dataset.
- `filters`: the filters applied to the dataset. Each item describes:
- `field`: which field this filter is bound to.
- - `operator`: which type of filter it is. One of `in`, `notIn`. See "Operator types".
+ - `operator`: which type of filter it is. See "Operator types".
- `value`: the actual value selected by the user.
- `perPage`: number of records to show per page.
- `page`: the page that is visible.
@@ -172,8 +196,8 @@ function MyCustomPageTable() {
},
search: '',
filters: [
- { field: 'author', operator: 'in', value: 2 },
- { field: 'status', operator: 'in', value: 'publish,draft' }
+ { field: 'author', operator: 'is', value: 2 },
+ { field: 'status', operator: 'isAny', value: [ 'publish', 'draft' ] }
],
hiddenFields: [ 'date', 'featured-image' ],
layout: {},
@@ -182,10 +206,10 @@ function MyCustomPageTable() {
const queryArgs = useMemo( () => {
const filters = {};
view.filters.forEach( ( filter ) => {
- if ( filter.field === 'status' && filter.operator === 'in' ) {
+ if ( filter.field === 'status' && filter.operator === 'isAny' ) {
filters.status = filter.value;
}
- if ( filter.field === 'author' && filter.operator === 'in' ) {
+ if ( filter.field === 'author' && filter.operator === 'is' ) {
filters.author = filter.value;
}
} );
@@ -284,12 +308,14 @@ Callback that signals the user triggered the details for one of more items, and
Allowed operators for fields of type `enumeration`:
-- `equal`: whether an item is equal to a single value.
-- `notEqual`: whether an item is not equal to a single value.
-- `in`: whether an item is in a list of values.
-- `notIn`: whether an item is not in a list of values.
+- `is`: whether the item is equal to a single value.
+- `isNot`: whether the item is not equal to a single value.
+- `isAny`: whether the item is present in a list of values.
+- `isNone`: whether the item is not present in a list of values.
+
+`is` and `isNot` are single-selection operators, while `isAny` and `isNone` are multi-selection. By default, a filter with no operators declared will support multi-selection. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item.
-By default, a field of type `enumeration` supports `in` and `notIn` operators — this is, it supports multiselection and negation. A filter cannot mix single-selection (`equal`, `notEqual`) & multi-selection (`in`, `notIn`) operators. If a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item.
+> The legacy operators `in` and `notIn` have been deprecated and will be removed soon. In the meantime, they work as `is` and `isNot` operators, respectively.
## Contributing to this package