UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

169 lines (156 loc) 5.53 kB
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 }`; };