@trackunit/react-form-components 0.0.142 → 0.0.144

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -6294,54 +6294,43 @@ const getPhoneNumberWithPlus = (phoneNumber) => {
6294
6294
  * A custom hook for managing phone number input state and validation.
6295
6295
  *
6296
6296
  * @returns {object} - An object containing state values and event handler functions.
6297
- * @property {string} countryCode - The current country code value.
6298
- * @property {string} phoneNumber - The current phone number value.
6299
- * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
6300
- * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
6301
- * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
6302
- * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
6297
+ * @property {Function} getIsCountryCodeInvalid - A function for check if country code is valid
6298
+ * @property {Function} getIsNumberInvalid- A function for check if number part of phone number is valid
6299
+ * @property {Function} getIsPhoneNumberValid- A function for check if phone number is valid
6300
+ * @property {Function} getPhoneNumber - A function for get formatted phone number with country code and plus sign
6303
6301
  * @property {string} combinedValue - The combined country code and phone number value.
6304
6302
  */
6305
- const usePhoneInput = (value) => {
6306
- const [countryCode, setCountryCode] = React.useState("");
6307
- const [phoneNumber, setPhoneNumber] = React.useState("");
6308
- const [isCountryCodeInvalid, setIsCountryCodeInvalid] = React.useState(false);
6309
- const [isPhoneNumberInvalid, setIsPhoneNumberInvalid] = React.useState(false);
6310
- const [isCombinedValueInvalid, setIsCombinedValueInvalid] = React.useState(false);
6311
- const [combinedValue, setCombinedValue] = React.useState(value !== null && value !== void 0 ? value : "");
6312
- React.useEffect(() => {
6313
- var _a, _b;
6314
- if (value && ((!countryCode && !phoneNumber) || !combinedValue)) {
6315
- const safePhoneNumber = getPhoneNumberWithPlus(value);
6316
- const number = parsePhoneNumber(safePhoneNumber);
6317
- setCountryCode((_a = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _a !== void 0 ? _a : "");
6318
- setPhoneNumber((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
6319
- setCombinedValue(`${countryCode}${phoneNumber}`);
6320
- combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
6303
+ const usePhoneInput = () => {
6304
+ const getIsCountryCodeInvalid = (code) => {
6305
+ if (code) {
6306
+ return false;
6321
6307
  }
6322
- }, [combinedValue, countryCode, phoneNumber, value]);
6323
- const handleCountryCodeChange = (option) => {
6324
- var _a;
6325
- setCountryCode((_a = option === null || option === void 0 ? void 0 : option.value) !== null && _a !== void 0 ? _a : "");
6326
- setIsCountryCodeInvalid(!Boolean(option === null || option === void 0 ? void 0 : option.value));
6327
- setCombinedValue(`${option === null || option === void 0 ? void 0 : option.value}${phoneNumber}`);
6328
- combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
6308
+ return true;
6309
+ };
6310
+ const getIsNumberInvalid = (number) => {
6311
+ if (number) {
6312
+ return false;
6313
+ }
6314
+ return true;
6315
+ };
6316
+ const getIsPhoneNumberInvalid = (phone) => {
6317
+ if (phone) {
6318
+ const normalized = getPhoneNumberWithPlus(phone.toString());
6319
+ return isPossiblePhoneNumber(normalized);
6320
+ }
6321
+ return true;
6329
6322
  };
6330
- const handlePhoneNumberChange = (event) => {
6331
- setPhoneNumber(event.target.value);
6332
- setIsPhoneNumberInvalid(!event.target.value || !Boolean(event.target.value.trim()));
6333
- setCombinedValue(`${countryCode}${event.target.value}`);
6334
- combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
6323
+ const getPhoneNumber = ({ country, phone }) => {
6324
+ if (country) {
6325
+ return getPhoneNumberWithPlus(`${country}${phone || ""}`);
6326
+ }
6327
+ return phone || "";
6335
6328
  };
6336
6329
  return {
6337
- countryCode,
6338
- phoneNumber,
6339
- isCountryCodeInvalid,
6340
- isPhoneNumberInvalid,
6341
- handleCountryCodeChange,
6342
- handlePhoneNumberChange,
6343
- combinedValue,
6344
- isCombinedValueInvalid,
6330
+ getIsCountryCodeInvalid,
6331
+ getIsNumberInvalid,
6332
+ getIsPhoneNumberInvalid,
6333
+ getPhoneNumber,
6345
6334
  };
6346
6335
  };
6347
6336
 
@@ -14503,7 +14492,7 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
14503
14492
  const filteredCodes = countryCodes.filter(code => {
14504
14493
  return !(excludedCountries === null || excludedCountries === void 0 ? void 0 : excludedCountries.some(excludedCode => excludedCode.value === code.value));
14505
14494
  });
14506
- return (jsxRuntime.jsx(Select, { dataTestId: dataTestId, className: "w-28", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, menuIsOpen: readOnly ? false : undefined }));
14495
+ return (jsxRuntime.jsx(Select, { dataTestId: dataTestId, className: "w-28", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, closeMenuOnSelect: true, menuIsOpen: readOnly ? false : undefined }));
14507
14496
  };
14508
14497
 
14509
14498
  /**
@@ -14517,9 +14506,43 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
14517
14506
  * @returns {JSX.Element} - The PhoneInput component.
14518
14507
  */
14519
14508
  const PhoneInput = React.forwardRef((_a, ref) => {
14520
- var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onChangeGetSeparateValues, onBlurCountryCode, readOnly, countryCodePlaceholder, onChangeCountryCode } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onChangeGetSeparateValues", "onBlurCountryCode", "readOnly", "countryCodePlaceholder", "onChangeCountryCode"]);
14509
+ var _b, _c;
14510
+ var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onBlurCountryCode, readOnly, countryCodePlaceholder, onChangeCountryCode, onBlur, onFieldsBlur, onChangeValue } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onBlurCountryCode", "readOnly", "countryCodePlaceholder", "onChangeCountryCode", "onBlur", "onFieldsBlur", "onChangeValue"]);
14511
+ const DEFAULT_COUNTRY_CODE = "+45";
14512
+ const { getIsCountryCodeInvalid, getIsPhoneNumberInvalid, getPhoneNumber } = usePhoneInput();
14513
+ const countryCodePlaceholderValue = countryCodePlaceholder || DEFAULT_COUNTRY_CODE;
14514
+ const safePhoneNumber = getPhoneNumberWithPlus(value || "");
14515
+ const number = parsePhoneNumber(safePhoneNumber);
14516
+ const [phone, setPhone] = React.useState((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
14517
+ const [code, setCode] = React.useState((_c = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _c !== void 0 ? _c : "");
14518
+ const codeRef = React.useRef(code); // synchronus reliable source of truth (country code is calling onblur just after onchange, useState is too slow for this)
14519
+ const phoneRef = React.useRef(phone); // synchronus reliable source of truth
14520
+ const onChangeHandler = React__default["default"].useCallback(({ newPhone, newCode }) => {
14521
+ onChangeValue &&
14522
+ onChangeValue({
14523
+ phone: getPhoneNumber({ country: newCode, phone: newPhone || "" }),
14524
+ countryCode: newCode,
14525
+ national: newPhone,
14526
+ });
14527
+ }, [onChangeValue, getPhoneNumber]);
14528
+ const onCodeChangeHandler = React__default["default"].useCallback((codeNumber) => {
14529
+ setCode(codeNumber);
14530
+ onChangeHandler({ newPhone: phone, newCode: codeNumber });
14531
+ }, [phone, onChangeHandler]);
14532
+ const onPhoneChangeHandler = React__default["default"].useCallback((phoneNumber) => {
14533
+ setPhone(phoneNumber);
14534
+ onChangeHandler({ newPhone: phoneNumber, newCode: code });
14535
+ }, [onChangeHandler, code]);
14536
+ const onBlurHandler = React__default["default"].useCallback(() => {
14537
+ onFieldsBlur &&
14538
+ onFieldsBlur({
14539
+ phone: getPhoneNumber({ country: codeRef.current, phone: phoneRef.current }),
14540
+ countryCode: codeRef.current,
14541
+ national: phoneRef.current,
14542
+ });
14543
+ }, [onFieldsBlur, getPhoneNumber]);
14521
14544
  const hiddenInputRef = React.useRef(null);
14522
- const { countryCode, phoneNumber, isCountryCodeInvalid, isPhoneNumberInvalid, handleCountryCodeChange, handlePhoneNumberChange, combinedValue, isCombinedValueInvalid, } = usePhoneInput(!!value ? value : defaultValue);
14545
+ const phoneNumber = getPhoneNumber({ country: code, phone });
14523
14546
  React.useEffect(() => {
14524
14547
  const element = hiddenInputRef.current;
14525
14548
  const event = new Event("change");
@@ -14527,15 +14550,19 @@ const PhoneInput = React.forwardRef((_a, ref) => {
14527
14550
  // The event as unknown as ChangeEvent<HTMLInputElement> assertion is needed because
14528
14551
  // the event's type is inferred as Event, which doesn't have a target property, whereas ChangeEvent does.
14529
14552
  onChange && onChange(event);
14530
- }, [combinedValue, onChange]);
14531
- React.useEffect(() => {
14532
- onChangeGetSeparateValues && onChangeGetSeparateValues({ countryCode, phoneNumber });
14533
- }, [countryCode, onChangeGetSeparateValues, phoneNumber]);
14534
- const countryCodePlaceholderValue = countryCodePlaceholder || "+45";
14535
- return (jsxRuntime.jsxs("div", { className: "grid grid-cols-min-fr gap-2", "data-testid": dataTestId && `${dataTestId}-container`, children: [jsxRuntime.jsx("input", { hidden: true, id: "phone-input", "data-testid": dataTestId, value: combinedValue, ref: hiddenInputRef, onChange: onChange, "aria-invalid": isCombinedValueInvalid || isInvalid }), jsxRuntime.jsx(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: countryCode, onChange: e => {
14536
- handleCountryCodeChange(e);
14537
- onChangeCountryCode && onChangeCountryCode(e);
14538
- }, disabled: disabled, readOnly: readOnly, isInvalid: isCountryCodeInvalid || isInvalid, placeholder: countryCodePlaceholderValue, onBlur: onBlurCountryCode }), jsxRuntime.jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phoneNumber, disabled: disabled, readOnly: readOnly, onChange: handlePhoneNumberChange, isInvalid: isPhoneNumberInvalid || isInvalid, actions: !disableAction && (jsxRuntime.jsx(ActionButton, { disabled: disabled || isInvalid || isCountryCodeInvalid || isPhoneNumberInvalid, value: `${countryCode}${phoneNumber}`, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] }));
14553
+ }, [phoneNumber, onChange]);
14554
+ return (jsxRuntime.jsxs("div", { className: "grid grid-cols-min-fr gap-2", "data-testid": dataTestId && `${dataTestId}-container`, children: [jsxRuntime.jsx("input", { hidden: true, id: "phone-input", "data-testid": dataTestId, value: phoneNumber, ref: hiddenInputRef, onChange: onChange, "aria-invalid": isInvalid }), jsxRuntime.jsx(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: code, onBlur: e => {
14555
+ onBlurHandler();
14556
+ }, onChange: e => {
14557
+ (e === null || e === void 0 ? void 0 : e.value) && onCodeChangeHandler(e === null || e === void 0 ? void 0 : e.value);
14558
+ codeRef.current = (e === null || e === void 0 ? void 0 : e.value) || "";
14559
+ }, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, placeholder: countryCodePlaceholderValue }), jsxRuntime.jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phone, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, onChange: e => {
14560
+ (e === null || e === void 0 ? void 0 : e.target.value) && setPhone(e.target.value);
14561
+ onPhoneChangeHandler(e.target.value);
14562
+ phoneRef.current = e === null || e === void 0 ? void 0 : e.target.value;
14563
+ }, onBlur: () => {
14564
+ onBlurHandler();
14565
+ }, actions: !disableAction && (jsxRuntime.jsx(ActionButton, { disabled: disabled || isInvalid || getIsCountryCodeInvalid(code || "") || getIsPhoneNumberInvalid(phone || " "), value: phoneNumber, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] }));
14539
14566
  });
14540
14567
 
14541
14568
  /**
@@ -14555,11 +14582,9 @@ const PhoneInput = React.forwardRef((_a, ref) => {
14555
14582
  * @returns {JSX.Element} - The PhoneField component.
14556
14583
  */
14557
14584
  const PhoneField = React.forwardRef((_a, ref) => {
14558
- var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId"]);
14559
- const { isCombinedValueInvalid, isPhoneNumberInvalid, isCountryCodeInvalid, combinedValue } = usePhoneInput(value);
14560
- const renderAsInvalid = isCombinedValueInvalid || isPhoneNumberInvalid || isCountryCodeInvalid || isInvalid || Boolean(errorMessage);
14585
+ var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId, onChangeValue, onFieldsBlur } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId", "onChangeValue", "onFieldsBlur"]);
14561
14586
  const htmlForId = id ? id : "phoneField-" + v4();
14562
- return (jsxRuntime.jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsxRuntime.jsx(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: combinedValue, defaultValue: defaultValue, ref: ref, dataTestId: dataTestId }, rest)) }));
14587
+ return (jsxRuntime.jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: isInvalid, helpText: (isInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsxRuntime.jsx(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: value, defaultValue: defaultValue, ref: ref, dataTestId: dataTestId, onChangeValue: onChangeValue, onFieldsBlur: onFieldsBlur }, rest)) }));
14563
14588
  });
14564
14589
 
14565
14590
  const cvaRadioGroup = cssClassVarianceUtilities.cvaMerge(["flex", "gap-2", "flex-col", "items-start"], {
package/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { registerTranslations } from '@trackunit/i18n-library-translation';
3
3
  import { IconButton, Icon, Tip, Heading, Text, MenuItem, Tag, Spinner } from '@trackunit/react-components';
4
4
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
5
5
  import * as React from 'react';
6
- import React__default, { useMemo, forwardRef, useState, cloneElement, useEffect, useCallback, useLayoutEffect, useContext, createElement, Fragment, createContext, useRef, Component } from 'react';
6
+ import React__default, { useMemo, forwardRef, useState, cloneElement, useCallback, useLayoutEffect, useContext, createElement, Fragment, createContext, useRef, useEffect, Component } from 'react';
7
7
  import { createPortal } from 'react-dom';
8
8
 
9
9
  var defaultTranslations = {
@@ -6268,54 +6268,43 @@ const getPhoneNumberWithPlus = (phoneNumber) => {
6268
6268
  * A custom hook for managing phone number input state and validation.
6269
6269
  *
6270
6270
  * @returns {object} - An object containing state values and event handler functions.
6271
- * @property {string} countryCode - The current country code value.
6272
- * @property {string} phoneNumber - The current phone number value.
6273
- * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
6274
- * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
6275
- * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
6276
- * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
6271
+ * @property {Function} getIsCountryCodeInvalid - A function for check if country code is valid
6272
+ * @property {Function} getIsNumberInvalid- A function for check if number part of phone number is valid
6273
+ * @property {Function} getIsPhoneNumberValid- A function for check if phone number is valid
6274
+ * @property {Function} getPhoneNumber - A function for get formatted phone number with country code and plus sign
6277
6275
  * @property {string} combinedValue - The combined country code and phone number value.
6278
6276
  */
6279
- const usePhoneInput = (value) => {
6280
- const [countryCode, setCountryCode] = useState("");
6281
- const [phoneNumber, setPhoneNumber] = useState("");
6282
- const [isCountryCodeInvalid, setIsCountryCodeInvalid] = useState(false);
6283
- const [isPhoneNumberInvalid, setIsPhoneNumberInvalid] = useState(false);
6284
- const [isCombinedValueInvalid, setIsCombinedValueInvalid] = useState(false);
6285
- const [combinedValue, setCombinedValue] = useState(value !== null && value !== void 0 ? value : "");
6286
- useEffect(() => {
6287
- var _a, _b;
6288
- if (value && ((!countryCode && !phoneNumber) || !combinedValue)) {
6289
- const safePhoneNumber = getPhoneNumberWithPlus(value);
6290
- const number = parsePhoneNumber(safePhoneNumber);
6291
- setCountryCode((_a = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _a !== void 0 ? _a : "");
6292
- setPhoneNumber((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
6293
- setCombinedValue(`${countryCode}${phoneNumber}`);
6294
- combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
6277
+ const usePhoneInput = () => {
6278
+ const getIsCountryCodeInvalid = (code) => {
6279
+ if (code) {
6280
+ return false;
6295
6281
  }
6296
- }, [combinedValue, countryCode, phoneNumber, value]);
6297
- const handleCountryCodeChange = (option) => {
6298
- var _a;
6299
- setCountryCode((_a = option === null || option === void 0 ? void 0 : option.value) !== null && _a !== void 0 ? _a : "");
6300
- setIsCountryCodeInvalid(!Boolean(option === null || option === void 0 ? void 0 : option.value));
6301
- setCombinedValue(`${option === null || option === void 0 ? void 0 : option.value}${phoneNumber}`);
6302
- combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
6282
+ return true;
6283
+ };
6284
+ const getIsNumberInvalid = (number) => {
6285
+ if (number) {
6286
+ return false;
6287
+ }
6288
+ return true;
6289
+ };
6290
+ const getIsPhoneNumberInvalid = (phone) => {
6291
+ if (phone) {
6292
+ const normalized = getPhoneNumberWithPlus(phone.toString());
6293
+ return isPossiblePhoneNumber(normalized);
6294
+ }
6295
+ return true;
6303
6296
  };
6304
- const handlePhoneNumberChange = (event) => {
6305
- setPhoneNumber(event.target.value);
6306
- setIsPhoneNumberInvalid(!event.target.value || !Boolean(event.target.value.trim()));
6307
- setCombinedValue(`${countryCode}${event.target.value}`);
6308
- combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
6297
+ const getPhoneNumber = ({ country, phone }) => {
6298
+ if (country) {
6299
+ return getPhoneNumberWithPlus(`${country}${phone || ""}`);
6300
+ }
6301
+ return phone || "";
6309
6302
  };
6310
6303
  return {
6311
- countryCode,
6312
- phoneNumber,
6313
- isCountryCodeInvalid,
6314
- isPhoneNumberInvalid,
6315
- handleCountryCodeChange,
6316
- handlePhoneNumberChange,
6317
- combinedValue,
6318
- isCombinedValueInvalid,
6304
+ getIsCountryCodeInvalid,
6305
+ getIsNumberInvalid,
6306
+ getIsPhoneNumberInvalid,
6307
+ getPhoneNumber,
6319
6308
  };
6320
6309
  };
6321
6310
 
@@ -14477,7 +14466,7 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
14477
14466
  const filteredCodes = countryCodes.filter(code => {
14478
14467
  return !(excludedCountries === null || excludedCountries === void 0 ? void 0 : excludedCountries.some(excludedCode => excludedCode.value === code.value));
14479
14468
  });
14480
- return (jsx$1(Select, { dataTestId: dataTestId, className: "w-28", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, menuIsOpen: readOnly ? false : undefined }));
14469
+ return (jsx$1(Select, { dataTestId: dataTestId, className: "w-28", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, closeMenuOnSelect: true, menuIsOpen: readOnly ? false : undefined }));
14481
14470
  };
14482
14471
 
14483
14472
  /**
@@ -14491,9 +14480,43 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
14491
14480
  * @returns {JSX.Element} - The PhoneInput component.
14492
14481
  */
14493
14482
  const PhoneInput = forwardRef((_a, ref) => {
14494
- var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onChangeGetSeparateValues, onBlurCountryCode, readOnly, countryCodePlaceholder, onChangeCountryCode } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onChangeGetSeparateValues", "onBlurCountryCode", "readOnly", "countryCodePlaceholder", "onChangeCountryCode"]);
14483
+ var _b, _c;
14484
+ var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onBlurCountryCode, readOnly, countryCodePlaceholder, onChangeCountryCode, onBlur, onFieldsBlur, onChangeValue } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onBlurCountryCode", "readOnly", "countryCodePlaceholder", "onChangeCountryCode", "onBlur", "onFieldsBlur", "onChangeValue"]);
14485
+ const DEFAULT_COUNTRY_CODE = "+45";
14486
+ const { getIsCountryCodeInvalid, getIsPhoneNumberInvalid, getPhoneNumber } = usePhoneInput();
14487
+ const countryCodePlaceholderValue = countryCodePlaceholder || DEFAULT_COUNTRY_CODE;
14488
+ const safePhoneNumber = getPhoneNumberWithPlus(value || "");
14489
+ const number = parsePhoneNumber(safePhoneNumber);
14490
+ const [phone, setPhone] = useState((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
14491
+ const [code, setCode] = useState((_c = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _c !== void 0 ? _c : "");
14492
+ const codeRef = useRef(code); // synchronus reliable source of truth (country code is calling onblur just after onchange, useState is too slow for this)
14493
+ const phoneRef = useRef(phone); // synchronus reliable source of truth
14494
+ const onChangeHandler = React__default.useCallback(({ newPhone, newCode }) => {
14495
+ onChangeValue &&
14496
+ onChangeValue({
14497
+ phone: getPhoneNumber({ country: newCode, phone: newPhone || "" }),
14498
+ countryCode: newCode,
14499
+ national: newPhone,
14500
+ });
14501
+ }, [onChangeValue, getPhoneNumber]);
14502
+ const onCodeChangeHandler = React__default.useCallback((codeNumber) => {
14503
+ setCode(codeNumber);
14504
+ onChangeHandler({ newPhone: phone, newCode: codeNumber });
14505
+ }, [phone, onChangeHandler]);
14506
+ const onPhoneChangeHandler = React__default.useCallback((phoneNumber) => {
14507
+ setPhone(phoneNumber);
14508
+ onChangeHandler({ newPhone: phoneNumber, newCode: code });
14509
+ }, [onChangeHandler, code]);
14510
+ const onBlurHandler = React__default.useCallback(() => {
14511
+ onFieldsBlur &&
14512
+ onFieldsBlur({
14513
+ phone: getPhoneNumber({ country: codeRef.current, phone: phoneRef.current }),
14514
+ countryCode: codeRef.current,
14515
+ national: phoneRef.current,
14516
+ });
14517
+ }, [onFieldsBlur, getPhoneNumber]);
14495
14518
  const hiddenInputRef = useRef(null);
14496
- const { countryCode, phoneNumber, isCountryCodeInvalid, isPhoneNumberInvalid, handleCountryCodeChange, handlePhoneNumberChange, combinedValue, isCombinedValueInvalid, } = usePhoneInput(!!value ? value : defaultValue);
14519
+ const phoneNumber = getPhoneNumber({ country: code, phone });
14497
14520
  useEffect(() => {
14498
14521
  const element = hiddenInputRef.current;
14499
14522
  const event = new Event("change");
@@ -14501,15 +14524,19 @@ const PhoneInput = forwardRef((_a, ref) => {
14501
14524
  // The event as unknown as ChangeEvent<HTMLInputElement> assertion is needed because
14502
14525
  // the event's type is inferred as Event, which doesn't have a target property, whereas ChangeEvent does.
14503
14526
  onChange && onChange(event);
14504
- }, [combinedValue, onChange]);
14505
- useEffect(() => {
14506
- onChangeGetSeparateValues && onChangeGetSeparateValues({ countryCode, phoneNumber });
14507
- }, [countryCode, onChangeGetSeparateValues, phoneNumber]);
14508
- const countryCodePlaceholderValue = countryCodePlaceholder || "+45";
14509
- return (jsxs("div", { className: "grid grid-cols-min-fr gap-2", "data-testid": dataTestId && `${dataTestId}-container`, children: [jsx$1("input", { hidden: true, id: "phone-input", "data-testid": dataTestId, value: combinedValue, ref: hiddenInputRef, onChange: onChange, "aria-invalid": isCombinedValueInvalid || isInvalid }), jsx$1(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: countryCode, onChange: e => {
14510
- handleCountryCodeChange(e);
14511
- onChangeCountryCode && onChangeCountryCode(e);
14512
- }, disabled: disabled, readOnly: readOnly, isInvalid: isCountryCodeInvalid || isInvalid, placeholder: countryCodePlaceholderValue, onBlur: onBlurCountryCode }), jsx$1(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phoneNumber, disabled: disabled, readOnly: readOnly, onChange: handlePhoneNumberChange, isInvalid: isPhoneNumberInvalid || isInvalid, actions: !disableAction && (jsx$1(ActionButton, { disabled: disabled || isInvalid || isCountryCodeInvalid || isPhoneNumberInvalid, value: `${countryCode}${phoneNumber}`, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] }));
14527
+ }, [phoneNumber, onChange]);
14528
+ return (jsxs("div", { className: "grid grid-cols-min-fr gap-2", "data-testid": dataTestId && `${dataTestId}-container`, children: [jsx$1("input", { hidden: true, id: "phone-input", "data-testid": dataTestId, value: phoneNumber, ref: hiddenInputRef, onChange: onChange, "aria-invalid": isInvalid }), jsx$1(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: code, onBlur: e => {
14529
+ onBlurHandler();
14530
+ }, onChange: e => {
14531
+ (e === null || e === void 0 ? void 0 : e.value) && onCodeChangeHandler(e === null || e === void 0 ? void 0 : e.value);
14532
+ codeRef.current = (e === null || e === void 0 ? void 0 : e.value) || "";
14533
+ }, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, placeholder: countryCodePlaceholderValue }), jsx$1(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phone, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, onChange: e => {
14534
+ (e === null || e === void 0 ? void 0 : e.target.value) && setPhone(e.target.value);
14535
+ onPhoneChangeHandler(e.target.value);
14536
+ phoneRef.current = e === null || e === void 0 ? void 0 : e.target.value;
14537
+ }, onBlur: () => {
14538
+ onBlurHandler();
14539
+ }, actions: !disableAction && (jsx$1(ActionButton, { disabled: disabled || isInvalid || getIsCountryCodeInvalid(code || "") || getIsPhoneNumberInvalid(phone || " "), value: phoneNumber, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] }));
14513
14540
  });
14514
14541
 
14515
14542
  /**
@@ -14529,11 +14556,9 @@ const PhoneInput = forwardRef((_a, ref) => {
14529
14556
  * @returns {JSX.Element} - The PhoneField component.
14530
14557
  */
14531
14558
  const PhoneField = forwardRef((_a, ref) => {
14532
- var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId"]);
14533
- const { isCombinedValueInvalid, isPhoneNumberInvalid, isCountryCodeInvalid, combinedValue } = usePhoneInput(value);
14534
- const renderAsInvalid = isCombinedValueInvalid || isPhoneNumberInvalid || isCountryCodeInvalid || isInvalid || Boolean(errorMessage);
14559
+ var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId, onChangeValue, onFieldsBlur } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId", "onChangeValue", "onFieldsBlur"]);
14535
14560
  const htmlForId = id ? id : "phoneField-" + v4();
14536
- return (jsx$1(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx$1(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: combinedValue, defaultValue: defaultValue, ref: ref, dataTestId: dataTestId }, rest)) }));
14561
+ return (jsx$1(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: isInvalid, helpText: (isInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx$1(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: value, defaultValue: defaultValue, ref: ref, dataTestId: dataTestId, onChangeValue: onChangeValue, onFieldsBlur: onFieldsBlur }, rest)) }));
14537
14562
  });
14538
14563
 
14539
14564
  const cvaRadioGroup = cvaMerge(["flex", "gap-2", "flex-col", "items-start"], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-form-components",
3
- "version": "0.0.142",
3
+ "version": "0.0.144",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -1,11 +1,16 @@
1
- import { FocusEventHandler } from "react";
2
- import { BaseInputProps } from "../BaseInput";
1
+ import React, { FocusEventHandler } from "react";
3
2
  import { SingleValue } from "react-select";
3
+ import { BaseInputProps } from "../BaseInput";
4
4
  type BaseInputExposedProps = Omit<BaseInputProps, "actions">;
5
5
  export interface PhoneNumberProps {
6
6
  countryCode: string;
7
7
  phoneNumber: string;
8
8
  }
9
+ interface PhoneNumberValue {
10
+ phone: string;
11
+ national?: string;
12
+ countryCode?: string;
13
+ }
9
14
  export interface PhoneInputProps extends BaseInputExposedProps {
10
15
  /**
11
16
  * To disable the action button.
@@ -16,10 +21,6 @@ export interface PhoneInputProps extends BaseInputExposedProps {
16
21
  * <PhoneInput disableAction />
17
22
  */
18
23
  disableAction?: boolean;
19
- /**
20
- * A callback function that gives you the country code and phone number as separate values.
21
- */
22
- onChangeGetSeparateValues?: (params: PhoneNumberProps) => void;
23
24
  /**
24
25
  * On blur handler for country code component.
25
26
  */
@@ -35,6 +36,14 @@ export interface PhoneInputProps extends BaseInputExposedProps {
35
36
  * A placeholder value for country code.
36
37
  */
37
38
  countryCodePlaceholder?: string;
39
+ /**
40
+ * An custom handler will be called when country code select or phone number input lost focus
41
+ */
42
+ onFieldsBlur?: ({ phone, national, countryCode }: PhoneNumberValue) => void;
43
+ /**
44
+ * An custom handler will be called when country code select or phone number input were changed
45
+ */
46
+ onChangeValue?: ({ phone, national, countryCode }: PhoneNumberValue) => void;
38
47
  }
39
48
  /**
40
49
  * A component for inputting phone numbers with an optional action button for initiating a phone call.
@@ -46,5 +55,5 @@ export interface PhoneInputProps extends BaseInputExposedProps {
46
55
  * @param {boolean} [disableAction=false] - Whether the action button is disabled or not.
47
56
  * @returns {JSX.Element} - The PhoneInput component.
48
57
  */
49
- export declare const PhoneInput: import("react").ForwardRefExoticComponent<PhoneInputProps & import("react").RefAttributes<HTMLInputElement>>;
58
+ export declare const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps & React.RefAttributes<HTMLInputElement>>;
50
59
  export {};
@@ -1,56 +1,20 @@
1
- /// <reference types="react" />
2
- export interface usePhoneInputProps {
3
- /**
4
- * The current country code value.
5
- */
6
- countryCode: string;
7
- /**
8
- * The current phone number value.
9
- */
10
- phoneNumber: string;
11
- /**
12
- * Whether the country code value is invalid.
13
- */
14
- isCountryCodeInvalid: boolean;
15
- /**
16
- * Whether the phone number value is invalid.
17
- */
18
- isPhoneNumberInvalid: boolean;
19
- /**
20
- * Whether the full phone number value is valid.
21
- *
22
- * @returns {boolean} Whether the full phone number value is valid.
23
- */
24
- isCombinedValueInvalid: boolean;
25
- /**
26
- * A function for handling changes to the country code input.
27
- */
28
- handleCountryCodeChange: (option: {
29
- label: string;
30
- value: string;
31
- } | null) => void;
32
- /**
33
- * A function for handling changes to the phone number input.
34
- */
35
- handlePhoneNumberChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
36
- /**
37
- * The combined country code and phone number value.
38
- *
39
- * @example "+123456789"
40
- * @returns {string} Combined phone number
41
- */
42
- combinedValue: string | number | readonly string[] | undefined;
1
+ export interface UsePhoneInput {
2
+ getIsCountryCodeInvalid: (phone?: string) => boolean;
3
+ getIsNumberInvalid: (phone: string) => boolean;
4
+ getIsPhoneNumberInvalid: (phone: string | number | readonly string[] | undefined) => boolean;
5
+ getPhoneNumber: ({ country, phone }: {
6
+ country?: string;
7
+ phone?: string;
8
+ }) => string;
43
9
  }
44
10
  /**
45
11
  * A custom hook for managing phone number input state and validation.
46
12
  *
47
13
  * @returns {object} - An object containing state values and event handler functions.
48
- * @property {string} countryCode - The current country code value.
49
- * @property {string} phoneNumber - The current phone number value.
50
- * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
51
- * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
52
- * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
53
- * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
14
+ * @property {Function} getIsCountryCodeInvalid - A function for check if country code is valid
15
+ * @property {Function} getIsNumberInvalid- A function for check if number part of phone number is valid
16
+ * @property {Function} getIsPhoneNumberValid- A function for check if phone number is valid
17
+ * @property {Function} getPhoneNumber - A function for get formatted phone number with country code and plus sign
54
18
  * @property {string} combinedValue - The combined country code and phone number value.
55
19
  */
56
- export declare const usePhoneInput: (value: string | number | readonly string[] | undefined) => usePhoneInputProps;
20
+ export declare const usePhoneInput: () => UsePhoneInput;