Пример #1
0
const withViewportMatch = ( queries ) => createHigherOrderComponent(
	withSelect( ( select ) => {
		return mapValues( queries, ( query ) => {
			return select( 'core/viewport' ).isViewportMatch( query );
		} );
	} ),
	'withViewportMatch'
);
Пример #2
0
const ifViewportMatches = ( query ) => createHigherOrderComponent(
	compose( [
		withViewportMatch( {
			isViewportMatch: query,
		} ),
		ifCondition( ( props ) => props.isViewportMatch ),
	] ),
	'ifViewportMatches'
);
Пример #3
0
export const withBlockEditContext = ( mapContextToProps ) => createHigherOrderComponent( ( OriginalComponent ) => {
	return ( props ) => (
		<Consumer>
			{ ( context ) => (
				<OriginalComponent
					{ ...props }
					{ ...mapContextToProps( context, props ) }
				/>
			) }
		</Consumer>
	);
}, 'withBlockEditContext' );
Пример #4
0
/**
 * Creates a higher-order component which adds filtering capability to the
 * wrapped component. Filters get applied when the original component is about
 * to be mounted. When a filter is added or removed that matches the hook name,
 * the wrapped component re-renders.
 *
 * @param {string} hookName Hook name exposed to be used by filters.
 *
 * @return {Function} Higher-order component factory.
 */
export default function withFilters( hookName ) {
	return createHigherOrderComponent( ( OriginalComponent ) => {
		return class FilteredComponent extends Component {
			/** @inheritdoc */
			constructor( props ) {
				super( props );

				this.onHooksUpdated = this.onHooksUpdated.bind( this );
				this.Component = applyFilters( hookName, OriginalComponent );
				this.namespace = uniqueId( 'core/with-filters/component-' );
				this.throttledForceUpdate = debounce( () => {
					this.Component = applyFilters( hookName, OriginalComponent );
					this.forceUpdate();
				}, ANIMATION_FRAME_PERIOD );

				addAction( 'hookRemoved', this.namespace, this.onHooksUpdated );
				addAction( 'hookAdded', this.namespace, this.onHooksUpdated );
			}

			/** @inheritdoc */
			componentWillUnmount() {
				this.throttledForceUpdate.cancel();
				removeAction( 'hookRemoved', this.namespace );
				removeAction( 'hookAdded', this.namespace );
			}

			/**
			 * When a filter is added or removed for the matching hook name, the wrapped component should re-render.
			 *
			 * @param {string} updatedHookName  Name of the hook that was updated.
			 */
			onHooksUpdated( updatedHookName ) {
				if ( updatedHookName === hookName ) {
					this.throttledForceUpdate();
				}
			}

			/** @inheritdoc */
			render() {
				return <this.Component { ...this.props } />;
			}
		};
	}, 'withFilters' );
}
Пример #5
0
const withMultipleValidation = createHigherOrderComponent( ( BlockEdit ) => {
	return enhance( ( {
		originalBlockClientId,
		selectFirst,
		...props
	} ) => {
		if ( ! originalBlockClientId ) {
			return <BlockEdit { ...props } />;
		}

		const blockType = getBlockType( props.name );
		const outboundType = getOutboundType( props.name );

		return [
			<div key="invalid-preview" style={ { minHeight: '60px' } }>
				<BlockEdit key="block-edit" { ...props } />
			</div>,
			<Warning
				key="multiple-use-warning"
				actions={ [
					<Button key="find-original" isLarge onClick={ selectFirst }>
						{ __( 'Find original' ) }
					</Button>,
					<Button key="remove" isLarge onClick={ () => props.onReplace( [] ) }>
						{ __( 'Remove' ) }
					</Button>,
					outboundType && (
						<Button
							key="transform"
							isLarge
							onClick={ () => props.onReplace(
								createBlock( outboundType.name, props.attributes )
							) }
						>
							{ __( 'Transform into:' ) }{ ' ' }
							{ outboundType.title }
						</Button>
					),
				] }
			>
				<strong>{ blockType.title }: </strong>
				{ __( 'This block can only be used once.' ) }
			</Warning>,
		];
	} );
}, 'withMultipleValidation' );
Пример #6
0
const withConstrainedTabbing = createHigherOrderComponent(
	( WrappedComponent ) => class extends Component {
		constructor() {
			super( ...arguments );

			this.focusContainRef = createRef();
			this.handleTabBehaviour = this.handleTabBehaviour.bind( this );
		}

		handleTabBehaviour( event ) {
			if ( event.keyCode !== TAB ) {
				return;
			}

			const tabbables = focus.tabbable.find( this.focusContainRef.current );
			if ( ! tabbables.length ) {
				return;
			}
			const firstTabbable = tabbables[ 0 ];
			const lastTabbable = tabbables[ tabbables.length - 1 ];

			if ( event.shiftKey && event.target === firstTabbable ) {
				event.preventDefault();
				lastTabbable.focus();
			} else if ( ! event.shiftKey && event.target === lastTabbable ) {
				event.preventDefault();
				firstTabbable.focus();
			/*
			 * When pressing Tab and none of the tabbables has focus, the keydown
			 * event happens on the wrapper div: move focus on the first tabbable.
			 */
			} else if ( ! tabbables.includes( event.target ) ) {
				event.preventDefault();
				firstTabbable.focus();
			}
		}

		render() {
			// Disable reason: this component is non-interactive, but must capture
			// events from the wrapped component to determine when the Tab key is used.
			/* eslint-disable jsx-a11y/no-static-element-interactions */
			return (
				<div
					onKeyDown={ this.handleTabBehaviour }
					ref={ this.focusContainRef }
					tabIndex="-1"
				>
					<WrappedComponent { ...this.props } />
				</div>
			);
			/* eslint-enable jsx-a11y/no-static-element-interactions */
		}
	},
	'withConstrainedTabbing'
);
Пример #7
0
/**
 * Creates a high-order components which adds ability to evalute
 * the conditional logic of fields.
 *
 * @param  {Function} input
 * @param  {Function} output
 * @return {Function}
 */
export default function withConditionalLogic( input, output ) {
	/**
	 * The function that controls the stream of side-effects.
	 *
	 * @param  {Object} component
	 * @param  {Object} props
	 * @return {Object}
	 */
	function aperture( component, props ) {
		if ( isEmpty( props.field.conditional_logic ) ) {
			return;
		}

		return input( props, component );
	}

	/**
	 * The function that causes the side effects.
	 *
	 * @param  {Object} props
	 * @return {Function}
	 */
	function handler( props ) {
		return function( effect ) {
			const { relation, rules } = props.field.conditional_logic;
			const data = output( props, effect );

			const results = rules.reduce( ( accumulator, rule ) => {
				if ( ! has( data, rule.field ) ) {
					// eslint-disable-next-line
					console.error(
						sprintf(
							__( 'An unknown field is used in condition - "%s"', 'carbon-fields-ui' ),
							rule.field
						)
					);

					return accumulator.concat( false );
				}

				// TODO: Handle the conditional logic for chained fields. Probably we'll need the id of each sibling.
				// See https://github.com/htmlburger/carbon-fields/commit/3628a86c8840c8323f45c829a96c512a9985ad10#diff-b1aea524a4b1ab510e28e01a54c25fcd
				const result = compare( data[ rule.field ], rule.compare, rule.value );

				return accumulator.concat( result );
			}, [] );

			let isVisible = false;

			switch ( relation ) {
				case 'AND':
					isVisible = every( results );
					break;

				case 'OR':
					isVisible = some( results );
					break;
			}

			if ( isVisible ) {
				props.showField( props.id );
			} else {
				props.hideField( props.id );
			}
		};
	}

	return createHigherOrderComponent( ( OriginalComponent ) => {
		return compose(
			withDispatch( ( dispatch ) => {
				const { showField, hideField } = dispatch( 'carbon-fields/core' );

				return {
					showField,
					hideField
				};
			} ),
			withSelect( ( select, props ) => ( {
				visible: select( 'carbon-fields/core' ).isFieldVisible( props.id )
			} ) ),
			withEffects( aperture, { handler } )
		)( OriginalComponent );
	}, 'withConditionalLogic' );
}
Пример #8
0
const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( WrappedComponent ) => {
	/**
	 * Default merge props. A constant value is used as the fallback since it
	 * can be more efficiently shallow compared in case component is repeatedly
 	 * rendered without its own merge props.
	 *
	 * @type {Object}
	 */
	const DEFAULT_MERGE_PROPS = {};

	/**
	 * Given a props object, returns the next merge props by mapSelectToProps.
	 *
	 * @param {Object} props Props to pass as argument to mapSelectToProps.
	 *
	 * @return {Object} Props to merge into rendered wrapped element.
	 */
	function getNextMergeProps( props ) {
		return (
			mapSelectToProps( props.registry.select, props.ownProps ) ||
			DEFAULT_MERGE_PROPS
		);
	}

	class ComponentWithSelect extends Component {
		constructor( props ) {
			super( props );

			this.onStoreChange = this.onStoreChange.bind( this );

			this.subscribe( props.registry );

			this.mergeProps = getNextMergeProps( props );
		}

		componentDidMount() {
			this.canRunSelection = true;

			// A state change may have occurred between the constructor and
			// mount of the component (e.g. during the wrapped component's own
			// constructor), in which case selection should be rerun.
			if ( this.hasQueuedSelection ) {
				this.hasQueuedSelection = false;
				this.onStoreChange();
			}
		}

		componentWillUnmount() {
			this.canRunSelection = false;
			this.unsubscribe();
		}

		shouldComponentUpdate( nextProps, nextState ) {
			// Cycle subscription if registry changes.
			const hasRegistryChanged = nextProps.registry !== this.props.registry;
			if ( hasRegistryChanged ) {
				this.unsubscribe();
				this.subscribe( nextProps.registry );
			}

			// Treat a registry change as equivalent to `ownProps`, to reflect
			// `mergeProps` to rendered component if and only if updated.
			const hasPropsChanged = (
				hasRegistryChanged ||
				! isShallowEqual( this.props.ownProps, nextProps.ownProps )
			);

			// Only render if props have changed or merge props have been updated
			// from the store subscriber.
			if ( this.state === nextState && ! hasPropsChanged ) {
				return false;
			}

			if ( hasPropsChanged ) {
				const nextMergeProps = getNextMergeProps( nextProps );
				if ( ! isShallowEqual( this.mergeProps, nextMergeProps ) ) {
					// If merge props change as a result of the incoming props,
					// they should be reflected as such in the upcoming render.
					// While side effects are discouraged in lifecycle methods,
					// this component is used heavily, and prior efforts to use
					// `getDerivedStateFromProps` had demonstrated miserable
					// performance.
					this.mergeProps = nextMergeProps;
				}

				// Regardless whether merge props are changing, fall through to
				// incur the render since the component will need to receive
				// the changed `ownProps`.
			}

			return true;
		}

		onStoreChange() {
			if ( ! this.canRunSelection ) {
				this.hasQueuedSelection = true;
				return;
			}

			const nextMergeProps = getNextMergeProps( this.props );
			if ( isShallowEqual( this.mergeProps, nextMergeProps ) ) {
				return;
			}

			this.mergeProps = nextMergeProps;

			// Schedule an update. Merge props are not assigned to state since
			// derivation of merge props from incoming props occurs within
			// shouldComponentUpdate, where setState is not allowed. setState
			// is used here instead of forceUpdate because forceUpdate bypasses
			// shouldComponentUpdate altogether, which isn't desireable if both
			// state and props change within the same render. Unfortunately,
			// this requires that next merge props are generated twice.
			this.setState( {} );
		}

		subscribe( registry ) {
			this.unsubscribe = registry.subscribe( this.onStoreChange );
		}

		render() {
			return <WrappedComponent { ...this.props.ownProps } { ...this.mergeProps } />;
		}
	}

	return ( ownProps ) => (
		<RegistryConsumer>
			{ ( registry ) => (
				<ComponentWithSelect
					ownProps={ ownProps }
					registry={ registry }
				/>
			) }
		</RegistryConsumer>
	);
}, 'withSelect' );
Пример #9
0
// } );

export const filters = [
	/**
	 * Automatically feature the first image added to an Artwork item.
	 */
	{
		hook: 'editor.BlockEdit',
		namespace: `artgallery/${ name }`,
		callback: createHigherOrderComponent( BlockEdit => withSelect( ( select, ownProps ) => {
			// Only proceed for image blocks with an assigned ID.
			if ( ownProps.name === 'core/image' && ownProps.attributes.id ) {
				const { getEditedPostAttribute } = select( 'core/editor' );

				if ( getEditedPostAttribute( 'type' ) === ARTWORK_POST_TYPE ) {

					if ( ! getEditedPostAttribute( 'featured_media' ) ) {
						// If this is an image block, if this post is an Artwork item, if there
						// is no existing featured image, AND if the image for this block has
						// been set, then automatically feature that image.
						dispatch( 'core/editor' ).editPost( {
							featured_media: ownProps.attributes.id,
						} );
					}
				}
			}
			return null;
		} )( BlockEdit ), 'autoFeatureFirstSelectedImage' ),
	},
];
Пример #10
0
export default createHigherOrderComponent(
	( WrappedComponent ) => {
		return class extends Component {
			constructor() {
				super( ...arguments );
				this.bindContainer = this.bindContainer.bind( this );
				this.focusNextRegion = this.focusRegion.bind( this, 1 );
				this.focusPreviousRegion = this.focusRegion.bind( this, -1 );
				this.onClick = this.onClick.bind( this );
				this.state = {
					isFocusingRegions: false,
				};
			}

			bindContainer( ref ) {
				this.container = ref;
			}

			focusRegion( offset ) {
				const regions = [ ...this.container.querySelectorAll( '[role="region"]' ) ];
				if ( ! regions.length ) {
					return;
				}
				let nextRegion = regions[ 0 ];
				const selectedIndex = regions.indexOf( document.activeElement );
				if ( selectedIndex !== -1 ) {
					let nextIndex = selectedIndex + offset;
					nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex;
					nextIndex = nextIndex === regions.length ? 0 : nextIndex;
					nextRegion = regions[ nextIndex ];
				}

				nextRegion.focus();
				this.setState( { isFocusingRegions: true } );
			}

			onClick() {
				this.setState( { isFocusingRegions: false } );
			}

			render() {
				const className = classnames( 'components-navigate-regions', {
					'is-focusing-regions': this.state.isFocusingRegions,
				} );

				// Disable reason: Clicking the editor should dismiss the regions focus style
				/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
				return (
					<div ref={ this.bindContainer } className={ className } onClick={ this.onClick }>
						<KeyboardShortcuts
							bindGlobal
							shortcuts={ {
								'ctrl+`': this.focusNextRegion,
								'ctrl+shift+`': this.focusPreviousRegion,
							} }
						/>
						<WrappedComponent { ...this.props } />
					</div>
				);
				/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
			}
		};
	}, 'navigateRegions'
);
Пример #11
0
const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( WrappedComponent ) => {
	/**
	 * Default merge props. A constant value is used as the fallback since it
	 * can be more efficiently shallow compared in case component is repeatedly
 	 * rendered without its own merge props.
	 *
	 * @type {Object}
	 */
	const DEFAULT_MERGE_PROPS = {};

	/**
	 * Given a props object, returns the next merge props by mapStateToProps.
	 *
	 * @param {Object} props Props to pass as argument to mapStateToProps.
	 *
	 * @return {Object} Props to merge into rendered wrapped element.
	 */
	function getNextMergeProps( props ) {
		return (
			mapStateToProps( props.registry.select, props.ownProps ) ||
			DEFAULT_MERGE_PROPS
		);
	}

	const ComponentWithSelect = remountOnPropChange( 'registry' )( class extends Component {
		constructor( props ) {
			super( props );

			this.subscribe();

			this.mergeProps = getNextMergeProps( props );
		}

		componentDidMount() {
			this.canRunSelection = true;
		}

		componentWillUnmount() {
			this.canRunSelection = false;
			this.unsubscribe();
		}

		shouldComponentUpdate( nextProps, nextState ) {
			const hasPropsChanged = ! isShallowEqual( this.props.ownProps, nextProps.ownProps );

			// Only render if props have changed or merge props have been updated
			// from the store subscriber.
			if ( this.state === nextState && ! hasPropsChanged ) {
				return false;
			}

			// If merge props change as a result of the incoming props, they
			// should be reflected as such in the upcoming render.
			if ( hasPropsChanged ) {
				const nextMergeProps = getNextMergeProps( nextProps );
				if ( ! isShallowEqual( this.mergeProps, nextMergeProps ) ) {
					// Side effects are typically discouraged in lifecycle methods, but
					// this component is heavily used and this is the most performant
					// code we've found thus far.
					// Prior efforts to use `getDerivedStateFromProps` have demonstrated
					// miserable performance.
					this.mergeProps = nextMergeProps;
				}
			}

			return true;
		}

		subscribe() {
			const { subscribe } = this.props.registry;

			this.unsubscribe = subscribe( () => {
				if ( ! this.canRunSelection ) {
					return;
				}

				const nextMergeProps = getNextMergeProps( this.props );
				if ( isShallowEqual( this.mergeProps, nextMergeProps ) ) {
					return;
				}

				this.mergeProps = nextMergeProps;

				// Schedule an update. Merge props are not assigned to state
				// because derivation of merge props from incoming props occurs
				// within shouldComponentUpdate, where setState is not allowed.
				// setState is used here instead of forceUpdate because forceUpdate
				// bypasses shouldComponentUpdate altogether, which isn't desireable
				// if both state and props change within the same render.
				// Unfortunately this requires that next merge props are generated
				// twice.
				this.setState( {} );
			} );
		}

		render() {
			return <WrappedComponent { ...this.props.ownProps } { ...this.mergeProps } />;
		}
	} );

	return ( ownProps ) => (
		<RegistryConsumer>
			{ ( registry ) => (
				<ComponentWithSelect
					ownProps={ ownProps }
					registry={ registry }
				/>
			) }
		</RegistryConsumer>
	);
}, 'withSelect' );
Пример #12
0
export const withBlockEditContext = ( mapContextToProps ) => createHigherOrderComponent( ( OriginalComponent ) => {
	return ( props ) => (
		<Consumer>
			{ ( context ) => (
				<OriginalComponent
					{ ...props }
					{ ...mapContextToProps( context, props ) }
				/>
			) }
		</Consumer>
	);
}, 'withBlockEditContext' );

/**
 * A Higher Order Component used to render conditionally the wrapped
 * component only when the BlockEdit has selected state set.
 *
 * @param {Component} OriginalComponent Component to wrap.
 *
 * @return {Component} Component which renders only when the BlockEdit is selected.
 */
export const ifBlockEditSelected = createHigherOrderComponent( ( OriginalComponent ) => {
	return ( props ) => (
		<Consumer>
			{ ( { isSelected } ) => isSelected && (
				<OriginalComponent { ...props } />
			) }
		</Consumer>
	);
}, 'ifBlockEditSelected' );
Пример #13
0
export default createHigherOrderComponent( ( Component ) => {
	const applyWithSelect = withSelect( ( select, props ) => {
		const { compactInput, compactInputKey } = window.cf.config;
		const field = select( 'carbon-fields/metaboxes' ).getFieldById( props.id );
		const { value } = field;

		let name = props.name || field.name;

		/**
		 * Wrap top-level field names in compact input key.
		 *
		 * The fields in widgets don't need this prefix because
		 * their input is already compacted by the `widget` namespace.
		 */
		if ( compactInput && ! props.name && name.indexOf( 'widget-carbon_fields' ) === -1 ) {
			name = `${ compactInputKey }[${ name }]`;
		}

		return {
			field,
			name,
			value
		};
	} );

	const applyWithDispatch = withDispatch( ( dispatch ) => {
		const { updateFieldValue } = dispatch( 'carbon-fields/metaboxes' );

		return {
			onChange: updateFieldValue
		};
	} );

	return compose( applyWithSelect, applyWithDispatch )( Component );
}, 'withField' );
Пример #14
0
							/>
						) }
					</figure>
				</Fragment>
			);
		}
	};

export default createHigherOrderComponent(
	compose( [
		withSelect( ( select, ownProps ) => {
			const { guid, src } = ownProps.attributes;
			const { getEmbedPreview, isRequestingEmbedPreview } = select( 'core' );

			const url = !! guid && `https://videopress.com/v/${ guid }`;
			const preview = !! url && getEmbedPreview( url );

			const isFetchingEmbedPreview = !! url && isRequestingEmbedPreview( url );
			const isUploading = isBlobURL( src );

			return {
				isFetchingPreview: isFetchingEmbedPreview,
				isUploading,
				preview,
			};
		} ),
		VideoPressEdit,
	] ),
	'withVideoPressEdit'
);
Пример #15
0
/**
 * Higher-order component, which handles color logic for class generation
 * color value, retrieval and color attribute setting.
 *
 * @param {...(object|string)} args The arguments can be strings or objects. If the argument is an object,
 *                                  it should contain the color attribute name as key and the color context as value.
 *                                  If the argument is a string the value should be the color attribute name,
 *                                  the color context is computed by applying a kebab case transform to the value.
 *                                  Color context represents the context/place where the color is going to be used.
 *                                  The class name of the color is generated using 'has' followed by the color name
 *                                  and ending with the color context all in kebab case e.g: has-green-background-color.
 *
 *
 * @return {Function} Higher-order component.
 */
export default ( ...args ) => {
	const colorMap = reduce( args, ( colorObject, arg ) => {
		return {
			...colorObject,
			...( isString( arg ) ? { [ arg ]: kebabCase( arg ) } : arg ),
		};
	}, {} );

	return createHigherOrderComponent(
		compose( [
			withSelect( ( select ) => {
				const settings = select( 'core/editor' ).getEditorSettings();
				return {
					colors: get( settings, [ 'colors' ], DEFAULT_COLORS ),
				};
			} ),
			( WrappedComponent ) => {
				return class extends Component {
					constructor( props ) {
						super( props );

						this.setters = this.createSetters();
						this.colorUtils = {
							getMostReadableColor: this.getMostReadableColor.bind( this ),
						};

						this.state = {};
					}

					getMostReadableColor( colorValue ) {
						const { colors } = this.props;
						return getMostReadableColor( colors, colorValue );
					}

					createSetters() {
						return reduce( colorMap, ( settersAccumulator, colorContext, colorAttributeName ) => {
							const upperFirstColorAttributeName = upperFirst( colorAttributeName );
							const customColorAttributeName = `custom${ upperFirstColorAttributeName }`;
							settersAccumulator[ `set${ upperFirstColorAttributeName }` ] =
								this.createSetColor( colorAttributeName, customColorAttributeName );
							return settersAccumulator;
						}, {} );
					}

					createSetColor( colorAttributeName, customColorAttributeName ) {
						return ( colorValue ) => {
							const colorObject = getColorObjectByColorValue( this.props.colors, colorValue );
							this.props.setAttributes( {
								[ colorAttributeName ]: colorObject && colorObject.slug ? colorObject.slug : undefined,
								[ customColorAttributeName ]: colorObject && colorObject.slug ? undefined : colorValue,
							} );
						};
					}

					static getDerivedStateFromProps( { attributes, colors }, previousState ) {
						return reduce( colorMap, ( newState, colorContext, colorAttributeName ) => {
							const colorObject = getColorObjectByAttributeValues(
								colors,
								attributes[ colorAttributeName ],
								attributes[ `custom${ upperFirst( colorAttributeName ) }` ],
							);

							const previousColorObject = previousState[ colorAttributeName ];
							const previousColor = get( previousColorObject, [ 'color' ] );
							/**
							* The "and previousColorObject" condition checks that a previous color object was already computed.
							* At the start previousColorObject and colorValue are both equal to undefined
							* bus as previousColorObject does not exist we should compute the object.
							*/
							if ( previousColor === colorObject.color && previousColorObject ) {
								newState[ colorAttributeName ] = previousColorObject;
							} else {
								newState[ colorAttributeName ] = {
									...colorObject,
									class: getColorClassName( colorContext, colorObject.slug ),
								};
							}
							return newState;
						}, {} );
					}

					render() {
						return (
							<WrappedComponent
								{ ...{
									...this.props,
									colors: undefined,
									...this.state,
									...this.setters,
									colorUtils: this.colorUtils,
								} }
							/>
						);
					}
				};
			},
		] ),
		'withColors'
	);
};
const withLatestCheckin = createHigherOrderComponent(
	compose( [
		withSelect(
			( select, { registration, datetimeId } ) => {
				if ( ! isModelEntityOfModel(
					registration,
					'registration'
				) ) {
					return {};
				}
				const { getLatestCheckin } = select( 'eventespresso/core' );
				const { hasFinishedResolution } = select( 'core/data' );
				const checkInEntity = getLatestCheckin(
					registration.id,
					datetimeId
				);
				return {
					checkinEntity: checkInEntity || null,
					hasResolvedCheckin: hasFinishedResolution(
						'eventespresso/core',
						'getLatestCheckin',
						[ registration.id, datetimeId ]
					),
				};
			}
		),
		withDispatch(
			( dispatch, { registration, datetimeId } ) => {
				const { toggleCheckin } = dispatch( 'eventespresso/core' );
				return {
					onClick() {
						if (
							isModelEntityOfModel(
								registration,
								'registration'
							)
						) {
							toggleCheckin( registration.id, datetimeId );
						}
					},
				};
			}
		),
	] ),
	'withLatestCheckin'
);