@gravityforms/components
Version:
UI components for use in Gravity Forms development. Both React and vanilla js flavors.
337 lines (316 loc) • 9.02 kB
JavaScript
import { React } from '@gravityforms/libraries';
import { isObject, slugify } from '@gravityforms/utils';
import Icon from '../../elements/Icon';
import Image from '../../elements/Image';
import Text from '../../elements/Text';
export const NEEDS_I18N_LABEL = 'Needs i18n';
/**
* @function normalizeText
* @description Normalizes the provided text and trims leading and trailing spaces.
*
* @since 4.5.0
*
* @param {string} text The text to normalize.
*
* @return {string} The normalized text.
*/
export const normalizeText = ( text = '' ) => {
return text
.normalize( 'NFD' )
.replace( /[\u0300-\u036f]/g, '' )
.trim();
};
/**
* @function getId
* @description Get the id from the prefix and key provided.
*
* @since 4.5.0
*
* @param {string} prefix The id prefix.
* @param {string} key The id key.
*
* @return {string} The id.
*/
export const getId = ( prefix, key ) => slugify( `${ prefix }-${ key }` );
/**
* @function getSearchItem
* @description Get the search item.
*
* @since 4.5.0
*
* @param {string} idPrefix The id prefix.
*
* @return {object} The search item.
*/
export const getSearchItem = ( idPrefix ) => ( {
type: 'search',
id: getId( idPrefix, 'search' ),
} );
/**
* @function augmentListItems
* @description Augment list items to add id to the item.
*
* @since 4.5.0
*
* @param {Array} listItems The list items.
* @param {object} args Arguments to augment list items with.
* @param {boolean} args.hasSearch Whether the dropdown has search or not.
* @param {string} args.id The id prefix.
*
* @return {Array} The augmented list items.
*/
export const augmentListItems = ( listItems, { hasSearch = false, id: idPrefix = '' } ) => {
const newListItems = listItems.map( ( item, index ) => {
if ( item.type === 'group' ) {
return {
...item,
id: item?.label?.label
? getId( idPrefix, slugify( `${ item.label?.label || '' }-group` ) )
: getId( idPrefix, `${ index }-group` ),
items: [
...( item?.label ? [ {
...item.label,
id: getId( idPrefix, slugify( item.label?.label || '' ) ),
} ] : [] ),
...augmentListItems( item.items, { id: idPrefix } ),
],
};
}
return {
...item,
id: getId( idPrefix, item.value ),
};
} );
return [
...newListItems,
...( hasSearch ? [ getSearchItem( idPrefix ) ] : [] ),
];
};
/**
* @function getFlatItems
* @description Get the flat items from the items provided.
* If the item is a group, it will recursively get the flat items.
* If the item is a group label, it will skip it.
* If the returnProp is provided, it will return the property from the items.
*
* @since 5.5.0
*
* @param {Array} items The items.
* @param {string|undefined} returnProp The property to return from the items.
*
* @return {Array} The item ids.
*/
export const getFlatItems = ( items, returnProp ) => {
return items.flatMap( ( item ) => {
if ( item.type === 'group' ) {
return getFlatItems( item.items, returnProp );
}
if ( item.type === 'groupLabel' ) {
return [];
}
return returnProp ? item[ returnProp ] : item;
} );
};
/**
* @function getListItemsState
* @description Gets the state for list items.
*
* @since 4.5.0
*
* @param {Array} listItems The list items.
* @param {object} args Arguments to augment list items with.
* @param {boolean} args.hasSearch Whether the dropdown has search or not.
* @param {string} args.id The id prefix.
*
* @return {object} The list items state.
*/
export const getListItemsState = ( listItems, args = {} ) => {
const items = augmentListItems( listItems, args );
return {
ids: getFlatItems( items, 'id' ),
flatItems: getFlatItems( items ),
items,
};
};
/**
* @function filterListItems
* @description Filters list items based on the search value provided.
*
* @since 4.5.0
*
* @param {Array} listItems The list items.
* @param {string} searchValue The search value.
*
* @return {Array} The filtered list items.
*/
export const filterListItems = ( listItems, searchValue ) => {
if ( searchValue === '' ) {
return listItems;
}
return listItems
.map( ( item ) => {
if ( item.type === 'group' ) {
// Recursively filter group items.
const filteredItems = filterListItems( item.items, searchValue );
if ( filteredItems.length > 0 ) {
return {
...item,
items: filteredItems,
};
}
return null; // Exclude empty groups.
}
// Non-group item, check if it matches the search.
const itemLabel = item.searchValue || item.label || '';
if ( ! itemLabel ) {
return null;
}
const normalizedLabel = normalizeText( itemLabel ).toLowerCase();
return normalizedLabel.indexOf( searchValue.toLowerCase() ) !== -1 ? item : null;
} )
.filter( Boolean );
};
/**
* @function getComponent
* @description Gets the component from the args provided.
*
* @since 4.5.0
*
* @param {JSX.Element|string|object} args The component args.
*
* @return {JSX.Element|string} The component.
*/
export const getComponent = ( args ) => {
// If args is a string or a React element, return it.
if ( typeof args === 'string' || React.isValidElement( args ) ) {
return args;
}
let component = null;
// If args is not an object, return null.
if ( ! isObject( args ) ) {
return component;
}
switch ( args.component ) {
case 'Icon':
component = <Icon { ...args.props } />;
break;
case 'Image':
component = <Image { ...args.props } />;
break;
case 'Text':
component = <Text { ...args.props } />;
break;
default:
component = null;
}
return component;
};
/**
* @function convertSingleToMultiItem
* @description Converts a single item to a multi item.
*
* @since 4.5.0
*
* @param {object} singleItem The single item.
*
* @return {Array} The multi item.
*/
export const convertSingleToMultiItem = ( singleItem ) => {
if ( ! isObject( singleItem ) ) {
// Single item is not object, something is wrong here, return single item.
return singleItem;
}
if ( Object.keys( singleItem ).length === 0 ) {
// Single item is empty, return empty array.
return [];
}
// Return single item as multi value.
return [ singleItem ];
};
/**
* @function convertMultiToSingleItem
* @description Converts a multi item to a single item.
*
* @since 4.5.0
*
* @param {Array} multiItem The multi item.
* @param {Array} listItems The current list items.
*
* @return {object} The single item.
*/
export const convertMultiToSingleItem = ( multiItem, listItems ) => {
if ( ! Array.isArray( multiItem ) ) {
// multi item is not array, something is wrong here, return multi item.
return multiItem;
}
if ( multiItem.length === 0 ) {
// Selected item is empty, return first item in list items.
return listItems[ 0 ] || {};
}
// Return first item in multi value.
return multiItem[ 0 ];
};
/**
* @function isListItemShape
* @description Check if the item is in the shape of a list item.
*
* @since 5.4.5
*
* @param {*} item The item to check.
*
* @return {boolean} Whether the item is in the shape of a list item.
*/
export const isListItemShape = ( item ) => {
return isObject( item ) &&
Object.prototype.hasOwnProperty.call( item, 'label' ) &&
Object.prototype.hasOwnProperty.call( item, 'value' );
};
/**
* @function getSelectedItemFromValue
* @description Get the selected item from the value provided.
*
* @since 5.4.5
*
* @param {string|number|Array|object} value The provided value.
* @param {Array} listItems The list items to get the selected item from, pass in flat list items if grouped.
* @param {boolean} multi Whether the dropdown is multi or not.
*
* @return {object|Array} The selected item.
*/
export const getSelectedItemFromValue = ( value, listItems, multi ) => {
// If the value is a string
if ( typeof value === 'string' || typeof value === 'number' ) {
const item = listItems.find( ( listItem ) => listItem.value === value );
if ( multi ) {
return item ? [ item ] : [];
}
return item || {};
}
// If the value is an array
if ( Array.isArray( value ) ) {
if ( multi ) {
return value.reduce( ( acc, val ) => {
// If val is a string, find the list item with the value and add it to the accumulator.
if ( typeof val === 'string' ) {
return [ ...acc, listItems.find( ( listItem ) => listItem.value === val ) ];
}
// If val is an object, add it to the accumulator.
if ( isListItemShape( val ) ) {
return [ ...acc, val ];
}
// If val is not a string or an object, return the accumulator.
return acc;
}, [] );
}
return convertMultiToSingleItem( value, listItems );
}
// If the value is an object
if ( isListItemShape( value ) ) {
if ( multi ) {
return [ value ];
}
return value;
}
// If the value is not a string, an array, or an object, return the default value.
return multi ? [] : {};
};