Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@alfalab/utils

Package Overview
Dependencies
Maintainers
0
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@alfalab/utils - npm Package Compare versions

Comparing version 1.17.0-rc.1 to 1.17.1

CHANGELOG.md

575

dist/esm/index.js

@@ -1,556 +0,19 @@

import { currency, countries } from '@alfalab/data';
import isPlainObject from 'lodash/isPlainObject';
import transform from 'lodash/transform';
/**
* Возвращает последние цифры номера счёта в формате `··XXXX`
* @param accountNumber - Номер счёта.
* @param additionalParams - Дополнительные параметры.
* @param additionalParams.countOfResultDigits - Количество оставшихся цифр. По умолчанию 4.
*/
function cropAccountNumber(accountNumber, { countOfResultDigits = 4 } = {}) {
if (!accountNumber) {
return accountNumber;
}
return `\u00B7\u00B7${accountNumber.slice(-countOfResultDigits)}`;
}
const ACCOUNT_SPLIT_REGEX = /^(\d{5})(\d{3})(\d)(\d{4})(\d{7})$/;
const ACCOUNT_FORMAT = '$1 $2 $3 $4 $5';
/**
* Возвращает отформатированное значение счёта.
* Разделяет пробелами число на группы.
* XXXXX XXX X XXXX XXXXXXX
*
* @param value Номер счёта.
*/
function formatAccount(value) {
if (!value) {
return '';
}
return value.replace(ACCOUNT_SPLIT_REGEX, ACCOUNT_FORMAT);
}
/**
* Возвращает знак валюты по ISO коду.
*
* @param currencyCode Код валюты.
*/
const getCurrencySymbol = (currencyCode) => currency.CURRENCY_SYMBOLS[currencyCode];
/**
* Дробит мажорную часть суммы на части по указанному символу.
*
* @param amount Сумма для разбивки на части
* @param partSize Размер частей суммы
* @param splitter Символ, разбивающий части суммы
* @param splitFrom Длина суммы, начиная с которой необходимо осуществлять разбивку. По-умолчанию длина
* равняется пяти по требованию гайдлайнов: https://design.alfabank.ru/patterns/amount. Пример: 2900 - не разбивается,
* 29 000 - разбивается.
*/
const splitAmount = (amount, partSize = 3, splitter, splitFrom = 5) => {
const splittingRegExp = `\\B(?=(\\d{${partSize}})+(?!\\d))`;
// Если длина суммы меньше требуемой, не форматируем сумму
if (amount.length < splitFrom) {
return amount;
}
return amount.replace(new RegExp(splittingRegExp, 'g'), splitter);
};
const AMOUNT_MAJOR_PART_SIZE = 3;
const AMOUNT_SPLIT_CODE_FROM = 4;
// Reference: https://jkorpela.fi/dashes.html
const NEGATIVE_SYMBOLS = {
/** Unicode: U+2212. An arithmetic operator */
'minus-sign': '−',
/** Unicode: U+002D. The Ascii hyphen. Used in inputs */
'hyphen-minus': '-',
};
const THINSP = String.fromCharCode(8201); // &thinsp;
const MMSP = String.fromCharCode(8287); // средний математический пробел
const AMOUNT_MAJOR_MINOR_PARTS_SEPARATOR = ',';
/**
* Возвращает код для валюты взависимости от формата
*/
const getCurrencyCodeWithFormat = (currency, codeFormat) => {
if (!currency) {
return '';
}
return codeFormat === 'symbolic'
? getCurrencySymbol(currency)
: currency;
};
/**
* Возвращает разделитель для кода валюты взависимости от формата
*/
const getCurrencySeparator = (codeFormat) => (codeFormat === 'symbolic' ? THINSP : MMSP);
/**
* Форматирует значение вместе с кодом валюты
*/
const formatWithCurrency = (value, currencyCode, separator) => [value, currencyCode].filter(Boolean).join(separator);
/**
* Форматирует значение суммы
* согласно гайдлайну https://design.alfabank.ru/patterns/amount
*/
const formatAmount = ({ value, currency, minority, view, negativeSymbol = 'minus-sign', codeFormat = 'symbolic', }) => {
const currencySymbol = getCurrencyCodeWithFormat(currency, codeFormat);
const currencySeparator = getCurrencySeparator(codeFormat);
if (value === null) {
return {
majorPart: '',
minorPart: '',
formatted: '',
currencySymbol,
currencySeparator,
formattedWithCurrency: formatWithCurrency(value, currencySymbol, currencySeparator),
};
}
// eslint-disable-next-line no-param-reassign
minority = minority === 0 ? 1 : minority; // because Math.log(0) => -Infinity
const fractionDigits = Math.log(minority) * Math.LOG10E;
const valueAbsStr = (Math.abs(value) / minority).toFixed(fractionDigits);
const [majorPart] = valueAbsStr.split('.');
let [, minorPart] = valueAbsStr.split('.');
if (view === 'default' && value % minority === 0) {
minorPart = '';
}
const majorPartSplitted = splitAmount(majorPart, AMOUNT_MAJOR_PART_SIZE, MMSP, AMOUNT_SPLIT_CODE_FROM);
const majorPartFormatted = value < 0 ? NEGATIVE_SYMBOLS[negativeSymbol] + majorPartSplitted : majorPartSplitted;
const formattedValueStr = minorPart
? majorPartFormatted + AMOUNT_MAJOR_MINOR_PARTS_SEPARATOR + minorPart
: majorPartFormatted;
return {
majorPart: majorPartFormatted,
minorPart,
currencySymbol,
formatted: formattedValueStr,
currencySeparator,
formattedWithCurrency: formatWithCurrency(formattedValueStr, currencySymbol, currencySeparator),
};
};
var Locale;
(function (Locale) {
Locale["RU"] = "ru";
Locale["EN"] = "en";
})(Locale || (Locale = {}));
const UNITS_COUNT = 4;
const SIZE_UNITS = {
[Locale.RU]: ['Б', 'КБ', 'МБ', 'ГБ'],
[Locale.EN]: ['B', 'KB', 'MB', 'GB'],
};
const humanizeNumberPartOfFileSize = (value, factor) => {
const maxFactor = UNITS_COUNT - 1;
if (value > 99 && factor === maxFactor)
return '99+';
return `${Number(value.toFixed(2))}`;
};
const parseFileSize = (fileSize) => {
const parsedFileSize = Number(fileSize);
if (Number.isNaN(parsedFileSize))
return 0;
return parsedFileSize;
};
/**
* Возвращает отформатированное значение размера файла.
* Разделяет пробелом число и единицу измерения.
*
* Примеры:
* 976.56 KB,
* 1000 B,
* 93.13 GB,
* 99+ GB - Если файл превышает 99 GB,
* 0 B - Если приходит строка, которую невозможно привести к числу
*/
const formatFileSize = (fileSize, locale = Locale.EN) => {
const maxFactor = UNITS_COUNT - 1;
let humanSize = parseFileSize(fileSize);
let factor = 0;
while (humanSize >= 1024 && factor < maxFactor) {
humanSize /= 1024;
factor += 1;
}
return `${humanizeNumberPartOfFileSize(humanSize, factor)} ${SIZE_UNITS[locale][factor]}`;
};
/**
* Удаляет форматирование номера телефона
* @param phone Отформатированный номер телефона с кодом страны
* @returns Номер телефона в формате 71112223344/88002223344
*/
const getRawPhoneNumber = (phone) => phone.replace(/\D+/g, '');
/**
* Форматирует номер телефона
* @param phone Номер телефона в любом формате с кодом страны
* @returns Номер телефона в формате +7 111 222-33-44/8 800 222-33-44
*/
const formatPhoneNumber = (phone) => {
const rawPhone = getRawPhoneNumber(phone);
const formattedPhone = rawPhone.replace(/(\d)(\d{3})(\d{3})(\d{2})(\d{2})/, `$1 $2 $3-$4-$5`);
return formattedPhone[0] === '7' ? '+'.concat(formattedPhone) : formattedPhone;
};
/**
* Маскирует номер телефона.
* Номер должен быть уже отформатирован.
* TODO: сделать, чтобы number можно было принимать любой (отформатированный/неотформатированный)
*
* @param {String} number Отформатированный номер телефона
* @returns {String}
*/
function maskPhoneNumber(number) {
const first = number.substr(0, 2);
const last = number.substr(number.length - 5, number.length);
return `${first} ··· ··· ${last}`;
}
const phoneNumber = {
format: formatPhoneNumber,
getRaw: getRawPhoneNumber,
mask: maskPhoneNumber,
};
function getAllCurrencyCodes() {
return Object.keys(currency.CURRENCY_SYMBOLS);
}
const formatCountry = ([_, name, iso2, dialCode, priority, areaCodes]) => ({
name,
iso2,
dialCode,
priority,
areaCodes: areaCodes !== null && areaCodes !== void 0 ? areaCodes : null,
});
const getCountries = () => countries.map(formatCountry).sort((a, b) => a.name.localeCompare(b.name));
const getCountriesHash = () => countries.reduce((acc, country) => {
const iso2 = country[2];
acc[iso2] = formatCountry(country);
return acc;
}, {});
/**
* Возвращает `true`, если элемент переполнен
* @param element HTML-элемент
*/
function isOverflown(element) {
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}
/**
* Возвращает слово в необходимой форме, зависимой от значения `count`.
*
* @example
* pluralize(1, 'день', 'дня', 'дней'); // день
* pluralize(2, 'день', 'дня', 'дней'); // дня
* pluralize(5, 'день', 'дня', 'дней'); // дней
*
* @param {number} count - Число.
* @param {string} nominative - Слово в именительном падеже единственного числа
* @param {string} genitive - Слово в родительном падеже единственного числа
* @param {string} plural - Слово во множественном числе
*
* @returns {string}
*/
const pluralize = (count, nominative, genitive, plural) => {
const normalizedNumber = count % 100;
if (normalizedNumber >= 11 && normalizedNumber <= 19) {
return plural;
}
const remainder = normalizedNumber % 10;
switch (remainder) {
case 1:
return nominative;
case 2:
case 3:
case 4:
return genitive;
default:
return plural;
}
};
/**
* Приводит секунды к формату `hh:mm:ss` и возвращает объект с ними.
*/
function secondsToTime(seconds) {
const h = Math.floor(seconds / 60 / 60);
const m = Math.floor(seconds / 60) % 60;
const s = seconds - h * 3600 - m * 60;
return {
hours: `${numPad('00', h)}`,
minutes: `${numPad('00', m)}`,
seconds: `${numPad('00', s)}`,
};
}
function numPad(pad, num) {
return typeof num === 'undefined' ? pad : (pad + num).slice(-pad.length);
}
/**
* Возвращает true/false при проверке номера валидности карты по алгоритму Луна
*/
const isValidCardNumber = (setValue) => {
let ch = 0;
const num = setValue.replace(/\D/g, '');
if (num === '')
return false;
for (let i = 0; i < num.length; i++) {
let n = parseInt(num[i], 10);
ch += 0 === i % 2 && (n *= 2) > 9 ? n - 9 : n;
}
return ch % 10 === 0;
};
const EMAIL_PARTS_SEPARATOR = '@';
const DOMAIN_PARTS_SEPARATOR = '.';
const MAX_ACCOUNT_LENGTH = 64;
const MAX_ADDRES_LENGTH = 255;
const MAX_DOMAIN_LENGTH = 63;
const EMAIL_REGEX = /^[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+\/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/;
/**
* Возвращает true для валидного email, иначе false
*
* @param email Строка содержащая email
*/
function isValidEmail(email) {
if (!email)
return false;
const emailParts = email.split(EMAIL_PARTS_SEPARATOR);
if (emailParts.length !== 2)
return false;
const [account, address] = emailParts;
const accountTooLong = account.length > MAX_ACCOUNT_LENGTH;
const addressTooLong = address.length > MAX_ADDRES_LENGTH;
if (accountTooLong || addressTooLong)
return false;
const domainParts = address.split(DOMAIN_PARTS_SEPARATOR);
const domainTooLong = domainParts.some((part) => part.length > MAX_DOMAIN_LENGTH);
if (domainTooLong)
return false;
return EMAIL_REGEX.test(email);
}
// prettier-ignore
const keyboardsLayouts = {
en: [
// нижний регистр
/* mac */ '§', /* win */ '`',
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']',
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '\\',
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
// нижний регистр через shift
/* mac */ '±', /* win */ '~',
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}',
'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '|',
'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
'@',
],
ru: [
// нижний регистр
'ё', 'ё',
'й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х', 'ъ',
'ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э', 'ё',
'я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю', '/',
// нижний регистр через shift
'Ё', 'Ё',
'Й', 'Ц', 'У', 'К', 'Е', 'Н', 'Г', 'Ш', 'Щ', 'З', 'Х', 'Ъ',
'Ф', 'Ы', 'В', 'А', 'П', 'Р', 'О', 'Л', 'Д', 'Ж', 'Э', 'Ё',
'Я', 'Ч', 'С', 'М', 'И', 'Т', 'Ь', 'Б', 'Ю', '?',
'"',
],
};
/** regex для раскладок клавиатуры */
const keyboardLanguages = {
en: /[a-z[\]{};:'"\\|,.<>?§±`~]/i,
ru: /[а-яё"/?]/i,
};
/**
* Проверяет строку на совпадение с раскладкой клавиатуры.
*/
function isKeyboardLayout(string, keyboardAlias) {
if (!string.length)
return false;
// your keyboard
const keyboardLanguage = keyboardLanguages[keyboardAlias];
// недопустимая раскладка клавиатуры
if (!keyboardLanguage)
return false;
let index = 0;
let isValid = false;
while (index < string.length) {
const letter = string[index];
if (keyboardLanguage.test(letter)) {
isValid = true;
return true;
}
index += 1;
}
return isValid;
}
/**
* Конвертирует символы из одной раскладки в другую.
*/
function keyboardSwitcher(characters = '', from = 'en', to = 'ru') {
// incoming text string
const textToConvert = characters;
// if characters is empty stop working
if (!textToConvert || !textToConvert.length)
return '';
// if incoming text and current keyboard not equal
if (!isKeyboardLayout(textToConvert, from)) {
return characters;
}
// current keyboard layout
const locale = keyboardsLayouts[from];
// convert characters to this keyboard layout
const localeOn = keyboardsLayouts[to];
let convertedText = '';
let index = 0;
while (index < textToConvert.length) {
const letter = textToConvert[index];
const letterIndex = locale.indexOf(letter);
convertedText += localeOn[letterIndex];
index += 1;
}
return convertedText;
}
function switchToKeyboard(characters, keyboardAlias) {
const textToConvert = characters;
if (!textToConvert.length)
return '';
const keyboards = Object.keys(keyboardLanguages);
let index = 0;
let convertedText = '';
while (index < textToConvert.length) {
const letter = textToConvert[index];
let locale = '';
keyboards.map((keyboardKey) => {
const isKeyboardIncludesLetter = isKeyboardLayout(letter, keyboardKey);
if (isKeyboardIncludesLetter && keyboardKey !== keyboardAlias) {
locale = keyboardKey;
return '';
}
return '';
});
const returnedLetter = locale === keyboardAlias ? letter : keyboardSwitcher(letter, locale, keyboardAlias);
convertedText += returnedLetter;
index += 1;
}
return convertedText;
}
/**
* Автоматически переводит английскую раскладку на русскую при вводе пользователем.
*/
const formatToRussian = (value = '', isCapitalize = false) => {
if (!value)
return '';
// check user keyboard and if it need convert to russian
const enteredValue = switchToKeyboard(value, 'ru');
return (isCapitalize ? enteredValue[0].toUpperCase() + enteredValue.substr(1) : enteredValue)
.replace(/[^\sа-яё-]/gi, '')
.replace(/\s{2,}/g, ' ');
};
/**
* Возвращает TRUE, если проскролено до низа страницы
*/
function hasScrolledToBottomOfPage() {
const { documentElement } = document;
const offset = documentElement.scrollTop + window.innerHeight;
const height = documentElement.offsetHeight;
return offset >= height;
}
/* eslint-disable no-param-reassign */
const NUMBER_REGEX = /^\d+$/;
const SKIP_NODE_NAMES = ['', null, undefined];
const nodeNamesToPath = (nodeNames) => nodeNames.reduce((pathAcc, nodeName) => {
if (!SKIP_NODE_NAMES.includes(nodeName)) {
if (nodeName === '*' || NUMBER_REGEX.test(`${nodeName}`)) {
pathAcc += '[*]';
}
else {
if (pathAcc) {
pathAcc += '.';
}
pathAcc += nodeName;
}
}
return pathAcc;
}, '');
const INVALID_FILTER_CONFIG_VALUES = ['', null, undefined, true, false];
const VALID_REPLACE_CONFIG_TYPES = ['string', 'number', 'boolean', 'function'];
const isReplacer = (replacer) => replacer === null || VALID_REPLACE_CONFIG_TYPES.includes(typeof replacer);
const prepareConfig = ({ replace: replaceConfig = {}, filter: filterConfig = [], }) => {
const filteredReplaceConfig = Object.keys(replaceConfig).reduce((res, replaceConfigKey) => {
const replacer = replaceConfig[replaceConfigKey];
if (isReplacer(replacer)) {
res[replaceConfigKey] = replacer;
}
return res;
}, {});
const filteredFilterConfigObject = filterConfig.reduce((res, path) => {
if (INVALID_FILTER_CONFIG_VALUES.includes(path)) {
return res;
}
return Object.assign(Object.assign({}, res), { [path]: true });
}, {});
const paths = [
...Object.keys(filteredFilterConfigObject),
...Object.keys(filteredReplaceConfig),
];
if (paths.length) {
return {
replaceConfig: filteredReplaceConfig,
filterConfig: filteredFilterConfigObject,
};
}
throw new TypeError('required replace or filter in transformConfig');
};
const getReplaceValue = (value, replacer) => {
if (isReplacer(replacer)) {
return typeof replacer === 'function' ? replacer(value) : replacer;
}
return value;
};
/* eslint-disable no-param-reassign */
// eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle
const _transformData = (data, { filterConfig, replaceConfig }, parentPath = '') => transform(data, (result, value, key) => {
const currentPath = nodeNamesToPath([parentPath, key]);
// Recurse into arrays and objects.
if (Array.isArray(value) || isPlainObject(value)) {
value = _transformData(value, {
filterConfig,
replaceConfig,
}, currentPath);
}
if (filterConfig[currentPath]) {
return;
}
const newValue = getReplaceValue(value, replaceConfig[currentPath]);
if (Array.isArray(result)) {
result.push(newValue);
return;
}
result[key] = newValue;
});
/**
* Преобразование полей объекта на основе конфига
*
* @param data {Object|Array} данные для преобразования
* @param config {TransformConfig} конфиг с функциями преобразования и путей для фильтра
*/
const transformData = (data, config) => _transformData(data, prepareConfig(config));
const transformDataUtils = {
getReplaceValue,
isReplacer,
nodeNamesToPath,
prepareConfig,
};
export { AMOUNT_MAJOR_MINOR_PARTS_SEPARATOR, Locale, MMSP, NEGATIVE_SYMBOLS, THINSP, cropAccountNumber, formatAccount, formatAmount, formatFileSize, formatToRussian, getAllCurrencyCodes, getCountries, getCountriesHash, getCurrencySymbol, hasScrolledToBottomOfPage, isKeyboardLayout, isOverflown, isValidCardNumber, isValidEmail, keyboardLanguages, keyboardSwitcher, keyboardsLayouts, phoneNumber, pluralize, secondsToTime, splitAmount, switchToKeyboard, transformData, transformDataUtils };
export { cropAccountNumber } from './crop-account-number/util.js';
export { formatAccount } from './format-account/util.js';
export { AMOUNT_MAJOR_MINOR_PARTS_SEPARATOR, MMSP, NEGATIVE_SYMBOLS, THINSP, formatAmount } from './format-amount/util.js';
export { formatFileSize } from './format-file-size/util.js';
export { Locale } from './format-file-size/locale.js';
export { phoneNumber } from './format-phone/index.js';
export { getAllCurrencyCodes } from './get-all-currency-codes/util.js';
export { getCountries, getCountriesHash } from './get-countries/util.js';
export { getCurrencySymbol } from './get-currency-symbol/util.js';
export { isOverflown } from './is-overflown/util.js';
export { pluralize } from './pluralize/util.js';
export { secondsToTime } from './seconds-to-time/util.js';
export { splitAmount } from './split-amount/util.js';
export { isValidCardNumber } from './is-valid-card-number/util.js';
export { isValidEmail } from './is-valid-email/util.js';
export { formatToRussian, isKeyboardLayout, keyboardLanguages, keyboardSwitcher, keyboardsLayouts, switchToKeyboard } from './keyboard-switcher/util.js';
export { hasScrolledToBottomOfPage } from './has-scrolled-to-bottom-of-page/util.js';
export { transformDataUtils } from './transform-data/index.js';
export { transformData } from './transform-data/util.js';
{
"name": "@alfalab/utils",
"version": "1.17.0-rc.1",
"version": "1.17.1",
"description": "common utils",

@@ -13,6 +13,7 @@ "sideEffects": false,

"scripts": {
"build": "yarn build:ts:es5 && yarn build:ts:es6 && yarn build:dts",
"build": "yarn build:ts:es5 && yarn build:ts:es6 && yarn build:dts && yarn size",
"build:ts:es5": "rollup --config './rollup.config.js'",
"build:ts:es6": "rollup --config './rollup.config.esm.js'",
"build:dts": "dts-bundle-generator --no-banner --config dts.json"
"build:dts": "dts-bundle-generator --no-banner --no-check --config dts.json",
"size": "yarn size-limit --json 2>&1 | grep -v '^$ ' > size-stat.json"
},

@@ -23,9 +24,7 @@ "keywords": [],

"dependencies": {
"@alfalab/data": "^1.8.0",
"lodash": "4.17.21"
"@alfalab/data": "^1.9.1",
"lodash": "^4.17.21"
},
"devDependencies": {
"@types/lodash": "4.14.182",
"dts-bundle-generator": "^6.12.0",
"rollup": "^2.38.0"
"@types/lodash": "4.14.182"
},

@@ -35,3 +34,3 @@ "publishConfig": {

},
"gitHead": "10dd0711fab88c3173da9cd72abe50194559d021"
"gitHead": "f02f9ff4bc3da6e3428324247da85f2b2f91a5e7"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc