-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Expand file tree
/
Copy pathparent-control.js
More file actions
133 lines (127 loc) · 3.67 KB
/
parent-control.js
File metadata and controls
133 lines (127 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { FormTokenField } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { useState, useEffect, useMemo } from '@wordpress/element';
import { useDebounce } from '@wordpress/compose';
/**
* Internal dependencies
*/
import { getEntitiesInfo, mapToIHasNameAndId } from '../../utils';
const EMPTY_ARRAY = [];
const BASE_QUERY = {
order: 'asc',
_fields: 'id,title',
context: 'view',
};
function ParentControl( { parents, postType, onChange } ) {
const [ search, setSearch ] = useState( '' );
const [ value, setValue ] = useState( EMPTY_ARRAY );
const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );
const debouncedSearch = useDebounce( setSearch, 250 );
const { searchResults, searchHasResolved } = useSelect(
( select ) => {
if ( ! search ) {
return { searchResults: EMPTY_ARRAY, searchHasResolved: true };
}
const { getEntityRecords, hasFinishedResolution } = select(
coreStore
);
const selectorArgs = [
'postType',
postType,
{
...BASE_QUERY,
search,
orderby: 'relevance',
exclude: parents,
per_page: 20,
},
];
return {
searchResults: getEntityRecords( ...selectorArgs ),
searchHasResolved: hasFinishedResolution(
'getEntityRecords',
selectorArgs
),
};
},
[ search, parents ]
);
const currentParents = useSelect(
( select ) => {
if ( ! parents?.length ) return EMPTY_ARRAY;
const { getEntityRecords } = select( coreStore );
return getEntityRecords( 'postType', postType, {
...BASE_QUERY,
include: parents,
per_page: parents.length,
} );
},
[ parents ]
);
// Update the `value` state only after the selectors are resolved
// to avoid emptying the input when we're changing parents.
useEffect( () => {
if ( ! parents?.length ) {
setValue( EMPTY_ARRAY );
}
if ( ! currentParents?.length ) return;
const currentParentsInfo = getEntitiesInfo(
mapToIHasNameAndId( currentParents, 'title.rendered' )
);
// Returns only the existing entity ids. This prevents the component
// from crashing in the editor, when non existing ids are provided.
const sanitizedValue = parents.reduce( ( accumulator, id ) => {
const entity = currentParentsInfo.mapById[ id ];
if ( entity ) {
accumulator.push( {
id,
value: entity.name,
} );
}
return accumulator;
}, [] );
setValue( sanitizedValue );
}, [ parents, currentParents ] );
const entitiesInfo = useMemo( () => {
if ( ! searchResults?.length ) return EMPTY_ARRAY;
return getEntitiesInfo(
mapToIHasNameAndId( searchResults, 'title.rendered' )
);
}, [ searchResults ] );
// Update suggestions only when the query has resolved.
useEffect( () => {
if ( ! searchHasResolved ) return;
setSuggestions( entitiesInfo.names );
}, [ entitiesInfo.names, searchHasResolved ] );
const getIdByValue = ( entitiesMappedByName, entity ) => {
const id = entity?.id || entitiesMappedByName?.[ entity ]?.id;
if ( id ) return id;
};
const onParentChange = ( newValue ) => {
const ids = Array.from(
newValue.reduce( ( accumulator, entity ) => {
// Verify that new values point to existing entities.
const id = getIdByValue( entitiesInfo.mapByName, entity );
if ( id ) accumulator.add( id );
return accumulator;
}, new Set() )
);
setSuggestions( EMPTY_ARRAY );
onChange( { parents: ids } );
};
return (
<FormTokenField
label={ __( 'Parents' ) }
value={ value }
onInputChange={ debouncedSearch }
suggestions={ suggestions }
onChange={ onParentChange }
/>
);
}
export default ParentControl;