@gravityforms/components
Version:
UI components for use in Gravity Forms development. Both React and vanilla js flavors.
169 lines (156 loc) • 5.53 kB
JavaScript
import { slugify } from '@gravityforms/utils';
const ANDROID_USER_AGENT_REG_EXP = /Android/i;
/**
* @function isAndroid
* @description Checks if the user agent is Android.
* Copied from the original function `isAndroid` from `input-format` package.
* The original function is not exported, so it was copied here.
*
* @since 5.5.0
*
* @return {boolean} Whether the user agent is Android.
*/
export const isAndroid = () => {
// `navigator` is not defined when running mocha tests.
if ( typeof navigator !== 'undefined' ) {
return ANDROID_USER_AGENT_REG_EXP.test( navigator.userAgent );
}
return false;
};
/**
* @function getCaretPosition
* @description Gets the caret position.
* Copied from the original function `getCaretPosition` from `input-format` package.
* The original function is not exported, so it was copied here.
*
* @since 5.5.0
*
* @param {HTMLElement} element The element to get the caret position from.
*
* @return {number} The caret position.
*/
export const getCaretPosition = ( element ) => {
return element.selectionStart;
};
/**
* @function setCaretPosition
* @description Sets the caret position.
* Copied from the original function `setCaretPosition` from `input-format` package.
* The original function is not exported, so it was copied here.
*
* @since 5.5.0
*
* @param {HTMLElement} element The element to set the caret position on.
* @param {number} caretPosition The caret position to set.
*/
export const setCaretPosition = ( element, caretPosition ) => {
// Sanity check
if ( caretPosition === undefined ) {
return;
}
// Set caret position.
// There has been an issue with caret positioning on Android devices.
// https://github.com/catamphetamine/input-format/issues/2
// I was revisiting this issue and looked for similar issues in other libraries.
// For example, there's [`text-mask`](https://github.com/text-mask/text-mask) library.
// They've had exactly the same issue when the caret seemingly refused to be repositioned programmatically.
// The symptoms were the same: whenever the caret passed through a non-digit character of a mask (a whitespace, a bracket, a dash, etc), it looked as if it placed itself one character before its correct position.
// https://github.com/text-mask/text-mask/issues/300
// They seem to have found a basic fix for it: calling `input.setSelectionRange()` in a timeout rather than instantly for Android devices.
// https://github.com/text-mask/text-mask/pull/400/files
// I've implemented the same workaround here.
if ( isAndroid() ) {
setTimeout( () => element.setSelectionRange( caretPosition, caretPosition ), 0 );
} else {
element.setSelectionRange( caretPosition, caretPosition );
}
};
/**
* @function getTextAfterEraseSelection
* @description Gets the text after erasing the selection.
* Modified from the original function `eraseSelection` from `input-format` package.
* The original function is not exported, so it was copied here.
*
* @since 5.5.0
*
* @param {string} text The text to erase the selection from.
* @param {object} selection The selection to erase. The selection is an object with `start` and `end` properties.
*
* @return {string} The text after erasing the selection.
*/
export const getTextAfterEraseSelection = ( text, selection ) => {
const start = Math.min( selection.start, selection.end );
const end = Math.max( selection.start, selection.end );
return text.slice( 0, start ) + text.slice( end );
};
/**
* @function getSelection
* @description Gets the selection.
* Copied from the original function `getSelection` from `input-format` package.
* The original function is not exported, so it was copied here.
*
* @since 5.5.0
*
* @param {HTMLElement} input The input element.
*
* @return {object|undefined} The selection object with `start` and `end` properties.
*/
export const getSelection = ( input ) => {
// If no selection, return nothing
if ( input.selectionStart === input.selectionEnd ) {
return;
}
const start = Math.min( input.selectionStart, input.selectionEnd );
const end = Math.max( input.selectionStart, input.selectionEnd );
return { start, end };
};
/**
* @function getId
* @description Get the id from the prefix and key provided.
*
* @since 5.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 e164ToNumber
* @description Convert an E.164 formatted phone number to a number.
*
* @since 5.5.0
*
* @throws {TypeError} If the input is not a string.
*
* @param {string} e164 The E.164 formatted phone number.
*
* @return {string} The phone number.
*/
export const e164ToNumber = ( e164 ) => {
if ( typeof e164 !== 'string' ) {
throw new TypeError( 'Input must be a string' );
}
return e164.replace( /^\+/, '' );
};
/**
* @function numberToE164
* @description Convert a phone number to E.164 format.
*
* @since 5.5.0
*
* @throws {TypeError} If the input is not a string.
*
* @param {string} number The phone number.
*
* @return {string} The E.164 formatted phone number.
*/
export const numberToE164 = ( number ) => {
if ( typeof number !== 'string' ) {
throw new TypeError( 'Input must be a string' );
}
return number.startsWith( '+' )
? number
: `+${ number }`;
};