@wordpress/dataviews 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +27 -5
  2. package/README.md +33 -30
  3. package/build/add-filter.js +30 -22
  4. package/build/add-filter.js.map +1 -1
  5. package/build/bulk-actions-toolbar.js +187 -0
  6. package/build/bulk-actions-toolbar.js.map +1 -0
  7. package/build/bulk-actions.js +75 -62
  8. package/build/bulk-actions.js.map +1 -1
  9. package/build/constants.js +17 -10
  10. package/build/constants.js.map +1 -1
  11. package/build/dataviews.js +64 -50
  12. package/build/dataviews.js.map +1 -1
  13. package/build/filter-and-sort-data-view.js +2 -2
  14. package/build/filter-and-sort-data-view.js.map +1 -1
  15. package/build/filter-summary.js +106 -96
  16. package/build/filter-summary.js.map +1 -1
  17. package/build/filters.js +18 -17
  18. package/build/filters.js.map +1 -1
  19. package/build/index.js.map +1 -1
  20. package/build/item-actions.js +101 -69
  21. package/build/item-actions.js.map +1 -1
  22. package/build/layouts.js.map +1 -1
  23. package/build/lock-unlock.js.map +1 -1
  24. package/build/normalize-fields.js.map +1 -1
  25. package/build/pagination.js +66 -57
  26. package/build/pagination.js.map +1 -1
  27. package/build/reset-filters.js +9 -4
  28. package/build/reset-filters.js.map +1 -1
  29. package/build/search-widget.js +108 -89
  30. package/build/search-widget.js.map +1 -1
  31. package/build/search.js +13 -6
  32. package/build/search.js.map +1 -1
  33. package/build/single-selection-checkbox.js +6 -2
  34. package/build/single-selection-checkbox.js.map +1 -1
  35. package/build/types.js.map +1 -1
  36. package/build/utils.js +3 -15
  37. package/build/utils.js.map +1 -1
  38. package/build/view-actions.js +168 -120
  39. package/build/view-actions.js.map +1 -1
  40. package/build/view-grid.js +119 -106
  41. package/build/view-grid.js.map +1 -1
  42. package/build/view-list.js +217 -83
  43. package/build/view-list.js.map +1 -1
  44. package/build/view-table.js +227 -199
  45. package/build/view-table.js.map +1 -1
  46. package/build-module/add-filter.js +30 -22
  47. package/build-module/add-filter.js.map +1 -1
  48. package/build-module/bulk-actions-toolbar.js +182 -0
  49. package/build-module/bulk-actions-toolbar.js.map +1 -0
  50. package/build-module/bulk-actions.js +77 -62
  51. package/build-module/bulk-actions.js.map +1 -1
  52. package/build-module/constants.js +16 -9
  53. package/build-module/constants.js.map +1 -1
  54. package/build-module/dataviews.js +65 -50
  55. package/build-module/dataviews.js.map +1 -1
  56. package/build-module/filter-and-sort-data-view.js +2 -2
  57. package/build-module/filter-and-sort-data-view.js.map +1 -1
  58. package/build-module/filter-summary.js +107 -97
  59. package/build-module/filter-summary.js.map +1 -1
  60. package/build-module/filters.js +18 -17
  61. package/build-module/filters.js.map +1 -1
  62. package/build-module/index.js.map +1 -1
  63. package/build-module/item-actions.js +102 -71
  64. package/build-module/item-actions.js.map +1 -1
  65. package/build-module/layouts.js.map +1 -1
  66. package/build-module/lock-unlock.js.map +1 -1
  67. package/build-module/normalize-fields.js.map +1 -1
  68. package/build-module/pagination.js +67 -57
  69. package/build-module/pagination.js.map +1 -1
  70. package/build-module/reset-filters.js +9 -4
  71. package/build-module/reset-filters.js.map +1 -1
  72. package/build-module/search-widget.js +109 -89
  73. package/build-module/search-widget.js.map +1 -1
  74. package/build-module/search.js +13 -6
  75. package/build-module/search.js.map +1 -1
  76. package/build-module/single-selection-checkbox.js +6 -2
  77. package/build-module/single-selection-checkbox.js.map +1 -1
  78. package/build-module/types.js.map +1 -1
  79. package/build-module/utils.js +2 -13
  80. package/build-module/utils.js.map +1 -1
  81. package/build-module/view-actions.js +170 -121
  82. package/build-module/view-actions.js.map +1 -1
  83. package/build-module/view-grid.js +121 -106
  84. package/build-module/view-grid.js.map +1 -1
  85. package/build-module/view-list.js +219 -85
  86. package/build-module/view-list.js.map +1 -1
  87. package/build-module/view-table.js +230 -201
  88. package/build-module/view-table.js.map +1 -1
  89. package/build-style/style-rtl.css +168 -44
  90. package/build-style/style.css +168 -44
  91. package/build-types/add-filter.d.ts +11 -0
  92. package/build-types/add-filter.d.ts.map +1 -0
  93. package/build-types/bulk-actions-toolbar.d.ts +12 -0
  94. package/build-types/bulk-actions-toolbar.d.ts.map +1 -0
  95. package/build-types/bulk-actions.d.ts +14 -0
  96. package/build-types/bulk-actions.d.ts.map +1 -0
  97. package/build-types/constants.d.ts +19 -32
  98. package/build-types/constants.d.ts.map +1 -1
  99. package/build-types/dataviews.d.ts +22 -0
  100. package/build-types/dataviews.d.ts.map +1 -0
  101. package/build-types/filter-and-sort-data-view.d.ts +3 -3
  102. package/build-types/filter-and-sort-data-view.d.ts.map +1 -1
  103. package/build-types/filter-summary.d.ts +14 -0
  104. package/build-types/filter-summary.d.ts.map +1 -0
  105. package/build-types/filters.d.ts +13 -0
  106. package/build-types/filters.d.ts.map +1 -0
  107. package/build-types/index.d.ts +4 -0
  108. package/build-types/index.d.ts.map +1 -0
  109. package/build-types/item-actions.d.ts +35 -0
  110. package/build-types/item-actions.d.ts.map +1 -0
  111. package/build-types/layouts.d.ts +24 -0
  112. package/build-types/layouts.d.ts.map +1 -0
  113. package/build-types/lock-unlock.d.ts +2 -0
  114. package/build-types/lock-unlock.d.ts.map +1 -0
  115. package/build-types/normalize-fields.d.ts +2 -2
  116. package/build-types/normalize-fields.d.ts.map +1 -1
  117. package/build-types/pagination.d.ts +16 -0
  118. package/build-types/pagination.d.ts.map +1 -0
  119. package/build-types/reset-filters.d.ts +13 -0
  120. package/build-types/reset-filters.d.ts.map +1 -0
  121. package/build-types/search-widget.d.ts +10 -0
  122. package/build-types/search-widget.d.ts.map +1 -0
  123. package/build-types/search.d.ts +13 -0
  124. package/build-types/search.d.ts.map +1 -0
  125. package/build-types/single-selection-checkbox.d.ts +17 -0
  126. package/build-types/single-selection-checkbox.d.ts.map +1 -0
  127. package/build-types/stories/fixtures.d.ts +114 -0
  128. package/build-types/stories/fixtures.d.ts.map +1 -0
  129. package/build-types/stories/index.story.d.ts +15 -0
  130. package/build-types/stories/index.story.d.ts.map +1 -0
  131. package/build-types/types.d.ts +221 -21
  132. package/build-types/types.d.ts.map +1 -1
  133. package/build-types/utils.d.ts +3 -0
  134. package/build-types/utils.d.ts.map +1 -0
  135. package/build-types/view-actions.d.ts +12 -0
  136. package/build-types/view-actions.d.ts.map +1 -0
  137. package/build-types/view-grid.d.ts +4 -0
  138. package/build-types/view-grid.d.ts.map +1 -0
  139. package/build-types/view-list.d.ts +4 -0
  140. package/build-types/view-list.d.ts.map +1 -0
  141. package/build-types/view-table.d.ts +5 -0
  142. package/build-types/view-table.d.ts.map +1 -0
  143. package/package.json +12 -13
  144. package/src/{add-filter.js → add-filter.tsx} +17 -1
  145. package/src/bulk-actions-toolbar.tsx +272 -0
  146. package/src/{bulk-actions.js → bulk-actions.tsx} +77 -17
  147. package/src/constants.ts +12 -5
  148. package/src/{dataviews.js → dataviews.tsx} +54 -14
  149. package/src/filter-and-sort-data-view.ts +13 -8
  150. package/src/{filter-summary.js → filter-summary.tsx} +38 -9
  151. package/src/{filters.js → filters.tsx} +18 -6
  152. package/src/{item-actions.js → item-actions.tsx} +119 -30
  153. package/src/normalize-fields.ts +4 -2
  154. package/src/{pagination.js → pagination.tsx} +29 -8
  155. package/src/{reset-filters.js → reset-filters.tsx} +17 -2
  156. package/src/{search-widget.js → search-widget.tsx} +27 -7
  157. package/src/{search.js → search.tsx} +22 -5
  158. package/src/{single-selection-checkbox.js → single-selection-checkbox.tsx} +17 -2
  159. package/src/style.scss +166 -43
  160. package/src/types.ts +286 -21
  161. package/src/{utils.js → utils.ts} +5 -13
  162. package/src/{view-actions.js → view-actions.tsx} +105 -49
  163. package/src/{view-grid.js → view-grid.tsx} +31 -18
  164. package/src/view-list.tsx +410 -0
  165. package/src/{view-table.js → view-table.tsx} +99 -40
  166. package/tsconfig.json +3 -4
  167. package/tsconfig.tsbuildinfo +1 -1
  168. package/build/dropdown-menu-helper.js +0 -71
  169. package/build/dropdown-menu-helper.js.map +0 -1
  170. package/build-module/dropdown-menu-helper.js +0 -64
  171. package/build-module/dropdown-menu-helper.js.map +0 -1
  172. package/src/dropdown-menu-helper.js +0 -61
  173. package/src/view-list.js +0 -207
  174. /package/src/{index.js → index.ts} +0 -0
  175. /package/src/{layouts.js → layouts.ts} +0 -0
  176. /package/src/{lock-unlock.js → lock-unlock.ts} +0 -0
@@ -15,13 +15,13 @@ import {
15
15
  OPERATOR_IS_NOT_ALL,
16
16
  } from './constants';
17
17
  import { normalizeFields } from './normalize-fields';
18
- import type { Data, Field, View } from './types';
18
+ import type { Field, AnyItem, View } from './types';
19
19
 
20
20
  function normalizeSearchInput( input = '' ) {
21
21
  return removeAccents( input.trim().toLowerCase() );
22
22
  }
23
23
 
24
- const EMPTY_ARRAY: Data = [];
24
+ const EMPTY_ARRAY: [] = [];
25
25
 
26
26
  /**
27
27
  * Applies the filtering, sorting and pagination to the raw data based on the view configuration.
@@ -32,11 +32,14 @@ const EMPTY_ARRAY: Data = [];
32
32
  *
33
33
  * @return Filtered, sorted and paginated data.
34
34
  */
35
- export function filterSortAndPaginate(
36
- data: Data,
35
+ export function filterSortAndPaginate< Item extends AnyItem >(
36
+ data: Item[],
37
37
  view: View,
38
- fields: Field[]
39
- ): { data: Data; paginationInfo: { totalItems: number; totalPages: number } } {
38
+ fields: Field< Item >[]
39
+ ): {
40
+ data: Item[];
41
+ paginationInfo: { totalItems: number; totalPages: number };
42
+ } {
40
43
  if ( ! data ) {
41
44
  return {
42
45
  data: EMPTY_ARRAY,
@@ -100,7 +103,9 @@ export function filterSortAndPaginate(
100
103
  ) {
101
104
  filteredData = filteredData.filter( ( item ) => {
102
105
  return filter.value.every( ( value: any ) => {
103
- return field.getValue( { item } ).includes( value );
106
+ return field
107
+ .getValue( { item } )
108
+ ?.includes( value );
104
109
  } );
105
110
  } );
106
111
  } else if (
@@ -111,7 +116,7 @@ export function filterSortAndPaginate(
111
116
  return filter.value.every( ( value: any ) => {
112
117
  return ! field
113
118
  .getValue( { item } )
114
- .includes( value );
119
+ ?.includes( value );
115
120
  } );
116
121
  } );
117
122
  } else if ( filter.operator === OPERATOR_IS ) {
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import classnames from 'classnames';
4
+ import clsx from 'clsx';
5
+ import type { RefObject } from 'react';
5
6
 
6
7
  /**
7
8
  * WordPress dependencies
@@ -35,8 +36,30 @@ import {
35
36
  OPERATOR_IS_ALL,
36
37
  OPERATOR_IS_NOT_ALL,
37
38
  } from './constants';
39
+ import type { Filter, NormalizedFilter, Operator, Option, View } from './types';
38
40
 
39
- const FilterText = ( { activeElements, filterInView, filter } ) => {
41
+ interface FilterTextProps {
42
+ activeElements: Option[];
43
+ filterInView?: Filter;
44
+ filter: NormalizedFilter;
45
+ }
46
+
47
+ interface OperatorSelectorProps {
48
+ filter: NormalizedFilter;
49
+ view: View;
50
+ onChangeView: ( view: View ) => void;
51
+ }
52
+
53
+ interface FilterSummaryProps extends OperatorSelectorProps {
54
+ addFilterRef: RefObject< HTMLButtonElement >;
55
+ openedFilter: string | null;
56
+ }
57
+
58
+ const FilterText = ( {
59
+ activeElements,
60
+ filterInView,
61
+ filter,
62
+ }: FilterTextProps ) => {
40
63
  if ( activeElements === undefined || activeElements.length === 0 ) {
41
64
  return filter.name;
42
65
  }
@@ -125,7 +148,11 @@ const FilterText = ( { activeElements, filterInView, filter } ) => {
125
148
  );
126
149
  };
127
150
 
128
- function OperatorSelector( { filter, view, onChangeView } ) {
151
+ function OperatorSelector( {
152
+ filter,
153
+ view,
154
+ onChangeView,
155
+ }: OperatorSelectorProps ) {
129
156
  const operatorOptions = filter.operators?.map( ( operator ) => ( {
130
157
  value: operator,
131
158
  label: OPERATORS[ operator ]?.label,
@@ -150,13 +177,14 @@ function OperatorSelector( { filter, view, onChangeView } ) {
150
177
  value={ value }
151
178
  options={ operatorOptions }
152
179
  onChange={ ( newValue ) => {
180
+ const operator = newValue as Operator;
153
181
  const newFilters = currentFilter
154
182
  ? [
155
183
  ...view.filters.map( ( _filter ) => {
156
184
  if ( _filter.field === filter.field ) {
157
185
  return {
158
186
  ..._filter,
159
- operator: newValue,
187
+ operator,
160
188
  };
161
189
  }
162
190
  return _filter;
@@ -166,7 +194,8 @@ function OperatorSelector( { filter, view, onChangeView } ) {
166
194
  ...view.filters,
167
195
  {
168
196
  field: filter.field,
169
- operator: newValue,
197
+ operator,
198
+ value: undefined,
170
199
  },
171
200
  ];
172
201
  onChangeView( {
@@ -188,8 +217,8 @@ export default function FilterSummary( {
188
217
  addFilterRef,
189
218
  openedFilter,
190
219
  ...commonProps
191
- } ) {
192
- const toggleRef = useRef();
220
+ }: FilterSummaryProps ) {
221
+ const toggleRef = useRef< HTMLDivElement >( null );
193
222
  const { filter, view, onChangeView } = commonProps;
194
223
  const filterInView = view.filters.find( ( f ) => f.field === filter.field );
195
224
  const activeElements = filter.elements.filter( ( element ) => {
@@ -220,7 +249,7 @@ export default function FilterSummary( {
220
249
  placement="top"
221
250
  >
222
251
  <div
223
- className={ classnames(
252
+ className={ clsx(
224
253
  'dataviews-filter-summary__chip',
225
254
  {
226
255
  'has-reset': canResetOrRemove,
@@ -253,7 +282,7 @@ export default function FilterSummary( {
253
282
  placement="top"
254
283
  >
255
284
  <button
256
- className={ classnames(
285
+ className={ clsx(
257
286
  'dataviews-filter-summary__chip-remove',
258
287
  { 'has-values': hasValues }
259
288
  ) }
@@ -2,6 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { memo, useRef } from '@wordpress/element';
5
+ import { __experimentalHStack as HStack } from '@wordpress/components';
5
6
 
6
7
  /**
7
8
  * Internal dependencies
@@ -11,17 +12,25 @@ import AddFilter from './add-filter';
11
12
  import ResetFilters from './reset-filters';
12
13
  import { sanitizeOperators } from './utils';
13
14
  import { ALL_OPERATORS, OPERATOR_IS, OPERATOR_IS_NOT } from './constants';
14
- import { __experimentalHStack as HStack } from '@wordpress/components';
15
+ import type { AnyItem, NormalizedField, NormalizedFilter, View } from './types';
15
16
 
16
- const Filters = memo( function Filters( {
17
+ interface FiltersProps< Item extends AnyItem > {
18
+ fields: NormalizedField< Item >[];
19
+ view: View;
20
+ onChangeView: ( view: View ) => void;
21
+ openedFilter: string | null;
22
+ setOpenedFilter: ( openedFilter: string | null ) => void;
23
+ }
24
+
25
+ function _Filters< Item extends AnyItem >( {
17
26
  fields,
18
27
  view,
19
28
  onChangeView,
20
29
  openedFilter,
21
30
  setOpenedFilter,
22
- } ) {
23
- const addFilterRef = useRef();
24
- const filters = [];
31
+ }: FiltersProps< Item > ) {
32
+ const addFilterRef = useRef< HTMLButtonElement >( null );
33
+ const filters: NormalizedFilter[] = [];
25
34
  fields.forEach( ( field ) => {
26
35
  if ( ! field.elements?.length ) {
27
36
  return;
@@ -108,6 +117,9 @@ const Filters = memo( function Filters( {
108
117
  { filterComponents }
109
118
  </HStack>
110
119
  );
111
- } );
120
+ }
121
+
122
+ // A type assertion is used here to keep the type argument.
123
+ const Filters = memo( _Filters ) as typeof _Filters;
112
124
 
113
125
  export default Filters;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { MouseEventHandler, ReactElement } from 'react';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
@@ -15,6 +20,7 @@ import { moreVertical } from '@wordpress/icons';
15
20
  * Internal dependencies
16
21
  */
17
22
  import { unlock } from './lock-unlock';
23
+ import type { Action, ActionModal as ActionModalType, AnyItem } from './types';
18
24
 
19
25
  const {
20
26
  DropdownMenuV2: DropdownMenu,
@@ -24,10 +30,51 @@ const {
24
30
  kebabCase,
25
31
  } = unlock( componentsPrivateApis );
26
32
 
27
- function ButtonTrigger( { action, onClick } ) {
33
+ export interface ActionTriggerProps< Item extends AnyItem > {
34
+ action: Action< Item >;
35
+ onClick: MouseEventHandler;
36
+ isBusy?: boolean;
37
+ items: Item[];
38
+ }
39
+
40
+ interface ActionModalProps< Item extends AnyItem > {
41
+ action: ActionModalType< Item >;
42
+ items: Item[];
43
+ closeModal?: () => void;
44
+ }
45
+
46
+ interface ActionWithModalProps< Item extends AnyItem >
47
+ extends ActionModalProps< Item > {
48
+ ActionTrigger: ( props: ActionTriggerProps< Item > ) => ReactElement;
49
+ isBusy?: boolean;
50
+ }
51
+
52
+ interface ActionsDropdownMenuGroupProps< Item extends AnyItem > {
53
+ actions: Action< Item >[];
54
+ item: Item;
55
+ }
56
+
57
+ interface ItemActionsProps< Item extends AnyItem > {
58
+ item: Item;
59
+ actions: Action< Item >[];
60
+ isCompact?: boolean;
61
+ }
62
+
63
+ interface CompactItemActionsProps< Item extends AnyItem > {
64
+ item: Item;
65
+ actions: Action< Item >[];
66
+ }
67
+
68
+ function ButtonTrigger< Item extends AnyItem >( {
69
+ action,
70
+ onClick,
71
+ items,
72
+ }: ActionTriggerProps< Item > ) {
73
+ const label =
74
+ typeof action.label === 'string' ? action.label : action.label( items );
28
75
  return (
29
76
  <Button
30
- label={ action.label }
77
+ label={ label }
31
78
  icon={ action.icon }
32
79
  isDestructive={ action.isDestructive }
33
80
  size="compact"
@@ -36,58 +83,91 @@ function ButtonTrigger( { action, onClick } ) {
36
83
  );
37
84
  }
38
85
 
39
- function DropdownMenuItemTrigger( { action, onClick } ) {
86
+ function DropdownMenuItemTrigger< Item extends AnyItem >( {
87
+ action,
88
+ onClick,
89
+ items,
90
+ }: ActionTriggerProps< Item > ) {
91
+ const label =
92
+ typeof action.label === 'string' ? action.label : action.label( items );
40
93
  return (
41
94
  <DropdownMenuItem
42
95
  onClick={ onClick }
43
- hideOnClick={ ! action.RenderModal }
96
+ hideOnClick={ ! ( 'RenderModal' in action ) }
44
97
  >
45
- <DropdownMenuItemLabel>{ action.label }</DropdownMenuItemLabel>
98
+ <DropdownMenuItemLabel>{ label }</DropdownMenuItemLabel>
46
99
  </DropdownMenuItem>
47
100
  );
48
101
  }
49
102
 
50
- function ActionWithModal( { action, item, ActionTrigger } ) {
103
+ export function ActionModal< Item extends AnyItem >( {
104
+ action,
105
+ items,
106
+ closeModal,
107
+ }: ActionModalProps< Item > ) {
108
+ const label =
109
+ typeof action.label === 'string' ? action.label : action.label( items );
110
+ return (
111
+ <Modal
112
+ title={ action.modalHeader || label }
113
+ __experimentalHideHeader={ !! action.hideModalHeader }
114
+ onRequestClose={ closeModal ?? ( () => {} ) }
115
+ overlayClassName={ `dataviews-action-modal dataviews-action-modal__${ kebabCase(
116
+ action.id
117
+ ) }` }
118
+ >
119
+ <action.RenderModal
120
+ items={ items }
121
+ closeModal={ closeModal }
122
+ onActionStart={ action.onActionStart }
123
+ onActionPerformed={ action.onActionPerformed }
124
+ />
125
+ </Modal>
126
+ );
127
+ }
128
+
129
+ export function ActionWithModal< Item extends AnyItem >( {
130
+ action,
131
+ items,
132
+ ActionTrigger,
133
+ isBusy,
134
+ }: ActionWithModalProps< Item > ) {
51
135
  const [ isModalOpen, setIsModalOpen ] = useState( false );
52
136
  const actionTriggerProps = {
53
137
  action,
54
- onClick: () => setIsModalOpen( true ),
138
+ onClick: () => {
139
+ setIsModalOpen( true );
140
+ },
141
+ items,
142
+ isBusy,
55
143
  };
56
- const { RenderModal, hideModalHeader } = action;
57
144
  return (
58
145
  <>
59
146
  <ActionTrigger { ...actionTriggerProps } />
60
147
  { isModalOpen && (
61
- <Modal
62
- title={ action.modalHeader || action.label }
63
- __experimentalHideHeader={ !! hideModalHeader }
64
- onRequestClose={ () => {
65
- setIsModalOpen( false );
66
- } }
67
- overlayClassName={ `dataviews-action-modal dataviews-action-modal__${ kebabCase(
68
- action.id
69
- ) }` }
70
- >
71
- <RenderModal
72
- items={ [ item ] }
73
- closeModal={ () => setIsModalOpen( false ) }
74
- />
75
- </Modal>
148
+ <ActionModal
149
+ action={ action }
150
+ items={ items }
151
+ closeModal={ () => setIsModalOpen( false ) }
152
+ />
76
153
  ) }
77
154
  </>
78
155
  );
79
156
  }
80
157
 
81
- function ActionsDropdownMenuGroup( { actions, item } ) {
158
+ export function ActionsDropdownMenuGroup< Item extends AnyItem >( {
159
+ actions,
160
+ item,
161
+ }: ActionsDropdownMenuGroupProps< Item > ) {
82
162
  return (
83
163
  <DropdownMenuGroup>
84
164
  { actions.map( ( action ) => {
85
- if ( !! action.RenderModal ) {
165
+ if ( 'RenderModal' in action ) {
86
166
  return (
87
167
  <ActionWithModal
88
168
  key={ action.id }
89
169
  action={ action }
90
- item={ item }
170
+ items={ [ item ] }
91
171
  ActionTrigger={ DropdownMenuItemTrigger }
92
172
  />
93
173
  );
@@ -97,6 +177,7 @@ function ActionsDropdownMenuGroup( { actions, item } ) {
97
177
  key={ action.id }
98
178
  action={ action }
99
179
  onClick={ () => action.callback( [ item ] ) }
180
+ items={ [ item ] }
100
181
  />
101
182
  );
102
183
  } ) }
@@ -104,7 +185,11 @@ function ActionsDropdownMenuGroup( { actions, item } ) {
104
185
  );
105
186
  }
106
187
 
107
- export default function ItemActions( { item, actions, isCompact } ) {
188
+ export default function ItemActions< Item extends AnyItem >( {
189
+ item,
190
+ actions,
191
+ isCompact,
192
+ }: ItemActionsProps< Item > ) {
108
193
  const { primaryActions, eligibleActions } = useMemo( () => {
109
194
  // If an action is eligible for all items, doesn't need
110
195
  // to provide the `isEligible` function.
@@ -134,12 +219,12 @@ export default function ItemActions( { item, actions, isCompact } ) {
134
219
  >
135
220
  { !! primaryActions.length &&
136
221
  primaryActions.map( ( action ) => {
137
- if ( !! action.RenderModal ) {
222
+ if ( 'RenderModal' in action ) {
138
223
  return (
139
224
  <ActionWithModal
140
225
  key={ action.id }
141
226
  action={ action }
142
- item={ item }
227
+ items={ [ item ] }
143
228
  ActionTrigger={ ButtonTrigger }
144
229
  />
145
230
  );
@@ -149,6 +234,7 @@ export default function ItemActions( { item, actions, isCompact } ) {
149
234
  key={ action.id }
150
235
  action={ action }
151
236
  onClick={ () => action.callback( [ item ] ) }
237
+ items={ [ item ] }
152
238
  />
153
239
  );
154
240
  } ) }
@@ -157,7 +243,10 @@ export default function ItemActions( { item, actions, isCompact } ) {
157
243
  );
158
244
  }
159
245
 
160
- function CompactItemActions( { item, actions } ) {
246
+ function CompactItemActions< Item extends AnyItem >( {
247
+ item,
248
+ actions,
249
+ }: CompactItemActionsProps< Item > ) {
161
250
  return (
162
251
  <DropdownMenu
163
252
  trigger={
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import type { Field, NormalizedField } from './types';
4
+ import type { Field, AnyItem, NormalizedField } from './types';
5
5
 
6
6
  /**
7
7
  * Apply default values and normalize the fields config.
@@ -9,7 +9,9 @@ import type { Field, NormalizedField } from './types';
9
9
  * @param fields Fields config.
10
10
  * @return Normalized fields config.
11
11
  */
12
- export function normalizeFields( fields: Field[] ): NormalizedField[] {
12
+ export function normalizeFields< Item extends AnyItem >(
13
+ fields: Field< Item >[]
14
+ ): NormalizedField< Item >[] {
13
15
  return fields.map( ( field ) => {
14
16
  const getValue = field.getValue || ( ( { item } ) => item[ field.id ] );
15
17
 
@@ -10,14 +10,29 @@ import { createInterpolateElement, memo } from '@wordpress/element';
10
10
  import { sprintf, __, _x } from '@wordpress/i18n';
11
11
  import { chevronRight, chevronLeft } from '@wordpress/icons';
12
12
 
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import type { View } from './types';
17
+
18
+ interface PaginationProps {
19
+ view: View;
20
+ onChangeView: ( view: View ) => void;
21
+ paginationInfo: {
22
+ totalItems: number;
23
+ totalPages: number;
24
+ };
25
+ }
26
+
13
27
  const Pagination = memo( function Pagination( {
14
28
  view,
15
29
  onChangeView,
16
30
  paginationInfo: { totalItems = 0, totalPages },
17
- } ) {
31
+ }: PaginationProps ) {
18
32
  if ( ! totalItems || ! totalPages ) {
19
33
  return null;
20
34
  }
35
+ const currentPage = view.page ?? 1;
21
36
  return (
22
37
  !! totalItems &&
23
38
  totalPages !== 1 && (
@@ -43,12 +58,15 @@ const Pagination = memo( function Pagination( {
43
58
  CurrentPageControl: (
44
59
  <SelectControl
45
60
  aria-label={ __( 'Current page' ) }
46
- value={ view.page }
61
+ value={ view.page?.toString() }
47
62
  options={ Array.from(
48
63
  Array( totalPages )
49
64
  ).map( ( _, i ) => {
50
65
  const page = i + 1;
51
- return { value: page, label: page };
66
+ return {
67
+ value: page.toString(),
68
+ label: page.toString(),
69
+ };
52
70
  } ) }
53
71
  onChange={ ( newValue ) => {
54
72
  onChangeView( {
@@ -56,7 +74,7 @@ const Pagination = memo( function Pagination( {
56
74
  page: +newValue,
57
75
  } );
58
76
  } }
59
- size={ 'compact' }
77
+ size="compact"
60
78
  __nextHasNoMarginBottom
61
79
  />
62
80
  ),
@@ -66,9 +84,12 @@ const Pagination = memo( function Pagination( {
66
84
  <HStack expanded={ false } spacing={ 1 }>
67
85
  <Button
68
86
  onClick={ () =>
69
- onChangeView( { ...view, page: view.page - 1 } )
87
+ onChangeView( {
88
+ ...view,
89
+ page: currentPage - 1,
90
+ } )
70
91
  }
71
- disabled={ view.page === 1 }
92
+ disabled={ currentPage === 1 }
72
93
  __experimentalIsFocusable
73
94
  label={ __( 'Previous page' ) }
74
95
  icon={ chevronLeft }
@@ -78,9 +99,9 @@ const Pagination = memo( function Pagination( {
78
99
  />
79
100
  <Button
80
101
  onClick={ () =>
81
- onChangeView( { ...view, page: view.page + 1 } )
102
+ onChangeView( { ...view, page: currentPage + 1 } )
82
103
  }
83
- disabled={ view.page >= totalPages }
104
+ disabled={ currentPage >= totalPages }
84
105
  __experimentalIsFocusable
85
106
  label={ __( 'Next page' ) }
86
107
  icon={ chevronRight }
@@ -4,8 +4,23 @@
4
4
  import { Button } from '@wordpress/components';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
- export default function ResetFilter( { filters, view, onChangeView } ) {
8
- const isPrimary = ( field ) =>
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import type { NormalizedFilter, View } from './types';
11
+
12
+ interface ResetFilterProps {
13
+ filters: NormalizedFilter[];
14
+ view: View;
15
+ onChangeView: ( view: View ) => void;
16
+ }
17
+
18
+ export default function ResetFilter( {
19
+ filters,
20
+ view,
21
+ onChangeView,
22
+ }: ResetFilterProps ) {
23
+ const isPrimary = ( field: string ) =>
9
24
  filters.some(
10
25
  ( _filter ) => _filter.field === field && _filter.isPrimary
11
26
  );
@@ -22,6 +22,7 @@ import { SVG, Circle } from '@wordpress/primitives';
22
22
  * Internal dependencies
23
23
  */
24
24
  import { unlock } from './lock-unlock';
25
+ import type { Filter, NormalizedFilter, View } from './types';
25
26
 
26
27
  const {
27
28
  CompositeV2: Composite,
@@ -29,6 +30,12 @@ const {
29
30
  useCompositeStoreV2: useCompositeStore,
30
31
  } = unlock( componentsPrivateApis );
31
32
 
33
+ interface SearchWidgetProps {
34
+ view: View;
35
+ filter: NormalizedFilter;
36
+ onChangeView: ( view: View ) => void;
37
+ }
38
+
32
39
  const radioCheck = (
33
40
  <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
34
41
  <Circle cx={ 12 } cy={ 12 } r={ 3 }></Circle>
@@ -39,8 +46,11 @@ function normalizeSearchInput( input = '' ) {
39
46
  return removeAccents( input.trim().toLowerCase() );
40
47
  }
41
48
 
42
- const EMPTY_ARRAY = [];
43
- const getCurrentValue = ( filterDefinition, currentFilter ) => {
49
+ const EMPTY_ARRAY: [] = [];
50
+ const getCurrentValue = (
51
+ filterDefinition: NormalizedFilter,
52
+ currentFilter?: Filter
53
+ ) => {
44
54
  if ( filterDefinition.singleSelection ) {
45
55
  return currentFilter?.value;
46
56
  }
@@ -56,7 +66,11 @@ const getCurrentValue = ( filterDefinition, currentFilter ) => {
56
66
  return EMPTY_ARRAY;
57
67
  };
58
68
 
59
- const getNewValue = ( filterDefinition, currentFilter, value ) => {
69
+ const getNewValue = (
70
+ filterDefinition: NormalizedFilter,
71
+ currentFilter: Filter | undefined,
72
+ value: any
73
+ ) => {
60
74
  if ( filterDefinition.singleSelection ) {
61
75
  return value;
62
76
  }
@@ -70,7 +84,7 @@ const getNewValue = ( filterDefinition, currentFilter, value ) => {
70
84
  return [ value ];
71
85
  };
72
86
 
73
- function ListBox( { view, filter, onChangeView } ) {
87
+ function ListBox( { view, filter, onChangeView }: SearchWidgetProps ) {
74
88
  const compositeStore = useCompositeStore( {
75
89
  virtualFocus: true,
76
90
  focusLoop: true,
@@ -184,7 +198,7 @@ function ListBox( { view, filter, onChangeView } ) {
184
198
  );
185
199
  }
186
200
 
187
- function ComboboxList( { view, filter, onChangeView } ) {
201
+ function ComboboxList( { view, filter, onChangeView }: SearchWidgetProps ) {
188
202
  const [ searchValue, setSearchValue ] = useState( '' );
189
203
  const deferredSearchValue = useDeferredValue( searchValue );
190
204
  const currentFilter = view.filters.find(
@@ -234,7 +248,13 @@ function ComboboxList( { view, filter, onChangeView } ) {
234
248
  setValue={ setSearchValue }
235
249
  >
236
250
  <div className="dataviews-search-widget-filter-combobox__wrapper">
237
- <Ariakit.ComboboxLabel render={ <VisuallyHidden /> }>
251
+ <Ariakit.ComboboxLabel
252
+ render={
253
+ <VisuallyHidden>
254
+ { __( 'Search items' ) }
255
+ </VisuallyHidden>
256
+ }
257
+ >
238
258
  { __( 'Search items' ) }
239
259
  </Ariakit.ComboboxLabel>
240
260
  <Ariakit.Combobox
@@ -290,7 +310,7 @@ function ComboboxList( { view, filter, onChangeView } ) {
290
310
  );
291
311
  }
292
312
 
293
- export default function SearchWidget( props ) {
313
+ export default function SearchWidget( props: SearchWidgetProps ) {
294
314
  const Widget = props.filter.elements.length > 10 ? ComboboxList : ListBox;
295
315
  return <Widget { ...props } />;
296
316
  }