@jobber/components 6.52.0 → 6.53.0
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/dist/InputPhoneNumber/InputMask.d.ts +7 -0
- package/dist/InputPhoneNumber/InputPhoneNumber.d.ts +2 -19
- package/dist/InputPhoneNumber/InputPhoneNumber.rebuilt.d.ts +3 -0
- package/dist/InputPhoneNumber/InputPhoneNumber.types.d.ts +56 -0
- package/dist/InputPhoneNumber/hooks/useInputPhoneActions.d.ts +16 -0
- package/dist/InputPhoneNumber/hooks/useInputPhoneFormField.d.ts +71 -0
- package/dist/InputPhoneNumber/index.cjs +247 -11
- package/dist/InputPhoneNumber/index.d.ts +5 -1
- package/dist/InputPhoneNumber/index.mjs +250 -10
- package/dist/InputPhoneNumber/useInputMask.d.ts +48 -0
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +1 -1
- package/dist/styles.css +2452 -2447
- package/package.json +2 -2
- package/dist/InputPhoneNumber-cjs.js +0 -100
- package/dist/InputPhoneNumber-es.js +0 -98
|
@@ -1,16 +1,256 @@
|
|
|
1
|
-
|
|
2
|
-
import '../tslib.es6-es.js';
|
|
3
|
-
import '
|
|
4
|
-
import '
|
|
5
|
-
import '
|
|
6
|
-
import 'framer-motion';
|
|
7
|
-
import '@jobber/design';
|
|
8
|
-
import 'classnames';
|
|
1
|
+
import React__default, { useState, useMemo, useCallback, useEffect, cloneElement, forwardRef, useId } from 'react';
|
|
2
|
+
import { _ as __rest } from '../tslib.es6-es.js';
|
|
3
|
+
import classnames from 'classnames';
|
|
4
|
+
import { j as useFormFieldWrapperStyles, b as useAtlantisFormFieldName, f as FormFieldWrapper, l as FormFieldPostFix, k as FormField } from '../FormField-es.js';
|
|
5
|
+
import { useFormContext, useForm } from 'react-hook-form';
|
|
9
6
|
import '../Button-es.js';
|
|
10
|
-
import '
|
|
7
|
+
import '@jobber/design';
|
|
8
|
+
import 'framer-motion';
|
|
11
9
|
import '../Icon-es.js';
|
|
12
|
-
import '../Typography-es.js';
|
|
13
10
|
import '../Text-es.js';
|
|
11
|
+
import '../Typography-es.js';
|
|
14
12
|
import '../useFormFieldFocus-es.js';
|
|
15
13
|
import '../InputValidation-es.js';
|
|
16
14
|
import '../Spinner-es.js';
|
|
15
|
+
import 'react-router-dom';
|
|
16
|
+
|
|
17
|
+
var styles = {"mask":"_78-Lxj78xPg-","hiddenValue":"GoiXVXaU1Qs-","emptyValue":"oOrjwubmsVA-","spinning":"T3VvmDmzs-4-"};
|
|
18
|
+
|
|
19
|
+
function useInputMask({ value = "", pattern, delimiter = "*", strict = true, onChange, }) {
|
|
20
|
+
const [isMasking, setIsMasking] = useState(!value);
|
|
21
|
+
const patternInfo = useMemo(() => {
|
|
22
|
+
const patternChars = pattern.split("");
|
|
23
|
+
const specialChars = patternChars.filter(char => char !== delimiter);
|
|
24
|
+
const maxCleanChars = patternChars.filter(char => char === delimiter).length;
|
|
25
|
+
return {
|
|
26
|
+
patternChars,
|
|
27
|
+
specialChars,
|
|
28
|
+
maxCleanChars,
|
|
29
|
+
};
|
|
30
|
+
}, [pattern, delimiter]);
|
|
31
|
+
const inputValue = useMemo(() => {
|
|
32
|
+
return value
|
|
33
|
+
.split("")
|
|
34
|
+
.filter(char => !patternInfo.specialChars.includes(char))
|
|
35
|
+
.join("");
|
|
36
|
+
}, [value, patternInfo.specialChars]);
|
|
37
|
+
const formatValue = useCallback((unformattedValue) => {
|
|
38
|
+
const { patternChars, specialChars, maxCleanChars } = patternInfo;
|
|
39
|
+
const cleanValueChars = unformattedValue
|
|
40
|
+
.split("")
|
|
41
|
+
.filter(char => !specialChars.includes(char) && !Number.isNaN(Number(char)));
|
|
42
|
+
const isOverCharLimit = cleanValueChars.length > maxCleanChars;
|
|
43
|
+
if (!strict && isOverCharLimit) {
|
|
44
|
+
return cleanValueChars.join("");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const formattedValue = patternChars.reduce(getMaskedValue([...cleanValueChars], specialChars), "");
|
|
48
|
+
return formattedValue;
|
|
49
|
+
}
|
|
50
|
+
}, [patternInfo, strict]);
|
|
51
|
+
const maskedOnChange = useCallback((newValue, event) => {
|
|
52
|
+
const formatted = formatValue(newValue);
|
|
53
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(formatted, event);
|
|
54
|
+
}, [formatValue, onChange]);
|
|
55
|
+
const formattedValue = useMemo(() => formatValue(value), [formatValue, value]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const shouldMask = strict || inputValue.length < patternInfo.maxCleanChars;
|
|
58
|
+
setIsMasking(shouldMask);
|
|
59
|
+
}, [inputValue, patternInfo.maxCleanChars, strict]);
|
|
60
|
+
const placeholderMask = useMemo(() => pattern
|
|
61
|
+
.replace(new RegExp(`\\${delimiter}`, "g"), "_")
|
|
62
|
+
.slice(value.length), [pattern, delimiter, value]);
|
|
63
|
+
return {
|
|
64
|
+
formattedValue,
|
|
65
|
+
placeholderMask,
|
|
66
|
+
isMasking,
|
|
67
|
+
maskedOnChange,
|
|
68
|
+
inputValue,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function getMaskedValue(cleanVal, specialChars) {
|
|
72
|
+
return (result, nextCharacter) => {
|
|
73
|
+
if (!cleanVal.length)
|
|
74
|
+
return result;
|
|
75
|
+
if (specialChars.includes(nextCharacter))
|
|
76
|
+
return result + nextCharacter;
|
|
77
|
+
const nextValue = cleanVal.shift();
|
|
78
|
+
return result + (nextValue !== undefined ? nextValue : "");
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function MaskElement({ isMasking, formattedValue, placeholderMask, }) {
|
|
83
|
+
if (!isMasking) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return (React__default.createElement("div", { className: styles.mask, "aria-hidden": "true" },
|
|
87
|
+
React__default.createElement("span", { className: styles.hiddenValue }, formattedValue),
|
|
88
|
+
React__default.createElement("span", null, placeholderMask)));
|
|
89
|
+
}
|
|
90
|
+
function InputMask({ children, delimiter = "*", pattern, strict = true, }) {
|
|
91
|
+
const { value: inputValue, onChange } = children.props;
|
|
92
|
+
const { placeholderMask, isMasking, formattedValue, maskedOnChange } = useInputMask({
|
|
93
|
+
value: String(inputValue || ""),
|
|
94
|
+
pattern,
|
|
95
|
+
delimiter,
|
|
96
|
+
strict,
|
|
97
|
+
onChange: onChange,
|
|
98
|
+
});
|
|
99
|
+
const inputMask = (React__default.createElement(MaskElement, { isMasking: isMasking, formattedValue: formattedValue, placeholderMask: placeholderMask }));
|
|
100
|
+
return cloneElement(children, {
|
|
101
|
+
onChange: maskedOnChange,
|
|
102
|
+
children: isMasking && inputMask,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const DEFAULT_PATTERN = "(***) ***-****";
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Combines the actions on the InputPhoneNumber such as onChange, onEnter, onFocus, onBlur, and onClear to forward information to the consumers of the InputPhoneNumber.
|
|
110
|
+
* Do not repeat this pattern. We are doing this as a proof of concept relating to the refactoring of Form inputs to see what can be removed.
|
|
111
|
+
*/
|
|
112
|
+
function useInputPhoneActions({ onChange, inputRef, onFocus, onBlur, onKeyDown, onEnter, }) {
|
|
113
|
+
function handleClear() {
|
|
114
|
+
var _a;
|
|
115
|
+
handleBlur();
|
|
116
|
+
onChange && onChange("");
|
|
117
|
+
(_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
118
|
+
}
|
|
119
|
+
function handleChange(event) {
|
|
120
|
+
const newValue = event.currentTarget.value;
|
|
121
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newValue, event);
|
|
122
|
+
}
|
|
123
|
+
function handleFocus(event) {
|
|
124
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
125
|
+
}
|
|
126
|
+
function handleKeyDown(event) {
|
|
127
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
128
|
+
if (!onEnter)
|
|
129
|
+
return;
|
|
130
|
+
if (event.key !== "Enter")
|
|
131
|
+
return;
|
|
132
|
+
if (event.shiftKey || event.ctrlKey)
|
|
133
|
+
return;
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
onEnter === null || onEnter === void 0 ? void 0 : onEnter(event);
|
|
136
|
+
}
|
|
137
|
+
function handleBlur(event) {
|
|
138
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
handleClear,
|
|
142
|
+
handleChange,
|
|
143
|
+
handleFocus,
|
|
144
|
+
handleBlur,
|
|
145
|
+
handleKeyDown,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Provides the props for the html fields rendered by the html input.
|
|
151
|
+
* DO not repeat this pattern. We are doing this as a proof of concept relating to the refactoring of Form inputs to see what can be removed.
|
|
152
|
+
*/
|
|
153
|
+
function useInputPhoneFormField(_a) {
|
|
154
|
+
var { id, name, description, inline, handleChange, handleBlur, handleFocus, handleKeyDown, error, disabled, autofocus, value, readonly } = _a, rest = __rest(_a, ["id", "name", "description", "inline", "handleChange", "handleBlur", "handleFocus", "handleKeyDown", "error", "disabled", "autofocus", "value", "readonly"]);
|
|
155
|
+
const descriptionIdentifier = `descriptionUUID--${id}`;
|
|
156
|
+
const fieldProps = Object.assign(Object.assign(Object.assign(Object.assign({}, rest), { id,
|
|
157
|
+
name,
|
|
158
|
+
disabled, autoFocus: autofocus, onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, onKeyDown: handleKeyDown, value, invalid: error || rest.invalid ? "true" : undefined }), (description &&
|
|
159
|
+
!inline && { "aria-describedby": descriptionIdentifier })), { readOnly: readonly });
|
|
160
|
+
return { fieldProps, descriptionIdentifier };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const InputPhoneNumberRebuilt = forwardRef(function InputPhoneNumberInternal(_a, ref) {
|
|
164
|
+
var _b, _c, _d, _e;
|
|
165
|
+
var { pattern = DEFAULT_PATTERN } = _a, props = __rest(_a, ["pattern"]);
|
|
166
|
+
const inputPhoneNumberRef = (_b = ref) !== null && _b !== void 0 ? _b : React__default.useRef(null);
|
|
167
|
+
const wrapperRef = React__default.useRef(null);
|
|
168
|
+
const { inputStyle } = useFormFieldWrapperStyles(Object.assign(Object.assign({}, props), { type: "tel" }));
|
|
169
|
+
const generatedId = useId();
|
|
170
|
+
const id = props.id || generatedId;
|
|
171
|
+
const { name } = useAtlantisFormFieldName({
|
|
172
|
+
nameProp: props.name,
|
|
173
|
+
id: id,
|
|
174
|
+
});
|
|
175
|
+
const { formattedValue, isMasking, placeholderMask, inputValue, maskedOnChange, } = useInputMask({
|
|
176
|
+
value: props.value,
|
|
177
|
+
pattern,
|
|
178
|
+
strict: false,
|
|
179
|
+
onChange: props.onChange,
|
|
180
|
+
});
|
|
181
|
+
const { handleChange, handleBlur, handleFocus, handleClear, handleKeyDown, } = useInputPhoneActions({
|
|
182
|
+
onChange: maskedOnChange,
|
|
183
|
+
onBlur: props.onBlur,
|
|
184
|
+
onFocus: props.onFocus,
|
|
185
|
+
onEnter: props.onEnter,
|
|
186
|
+
inputRef: inputPhoneNumberRef,
|
|
187
|
+
});
|
|
188
|
+
const { fieldProps, descriptionIdentifier } = useInputPhoneFormField({
|
|
189
|
+
id,
|
|
190
|
+
name,
|
|
191
|
+
handleChange,
|
|
192
|
+
handleBlur,
|
|
193
|
+
handleFocus,
|
|
194
|
+
handleKeyDown,
|
|
195
|
+
autofocus: props.autoFocus,
|
|
196
|
+
disabled: props.disabled,
|
|
197
|
+
readonly: props.readonly,
|
|
198
|
+
invalid: props.invalid,
|
|
199
|
+
error: props.error,
|
|
200
|
+
description: props.description,
|
|
201
|
+
inline: props.inline,
|
|
202
|
+
});
|
|
203
|
+
return (React__default.createElement(FormFieldWrapper, { disabled: props.disabled, size: props.size, inline: props.inline, wrapperRef: wrapperRef, error: (_c = props.error) !== null && _c !== void 0 ? _c : "", invalid: Boolean(props.error || props.invalid), identifier: id, descriptionIdentifier: descriptionIdentifier, description: props.description, clearable: (_d = props.clearable) !== null && _d !== void 0 ? _d : "never", onClear: handleClear, type: "tel", placeholder: props.placeholder, value: formattedValue, prefix: props.prefix, suffix: props.suffix, readonly: props.readonly, loading: props.loading },
|
|
204
|
+
React__default.createElement("input", Object.assign({ type: "tel" }, fieldProps, { ref: inputPhoneNumberRef, className: classnames(inputStyle, {
|
|
205
|
+
[styles.emptyValue]: inputValue.length === 0 && pattern[0] === "(",
|
|
206
|
+
}), value: formattedValue })),
|
|
207
|
+
React__default.createElement(MaskElement, { isMasking: isMasking, formattedValue: formattedValue, placeholderMask: placeholderMask }),
|
|
208
|
+
React__default.createElement(FormFieldPostFix, { variation: "spinner", visible: (_e = props.loading) !== null && _e !== void 0 ? _e : false })));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
function InputPhoneNumber$1(_a) {
|
|
212
|
+
var { required } = _a, props = __rest(_a, ["required"]);
|
|
213
|
+
const { placeholder, validations, pattern = DEFAULT_PATTERN } = props;
|
|
214
|
+
const errorSubject = placeholder || "Phone number";
|
|
215
|
+
const { getValues } = useFormContext() != undefined
|
|
216
|
+
? useFormContext()
|
|
217
|
+
: // If there isn't a Form Context being provided, get a form for this field.
|
|
218
|
+
useForm({ mode: "onTouched" });
|
|
219
|
+
return (React__default.createElement(InputMask, { pattern: pattern, strict: false },
|
|
220
|
+
React__default.createElement(FormField, Object.assign({}, props, { type: "tel", pattern: pattern, validations: Object.assign(Object.assign({ required: {
|
|
221
|
+
value: Boolean(required),
|
|
222
|
+
message: `${errorSubject} is required`,
|
|
223
|
+
} }, validations), { validate: getPhoneNumberValidation }) }))));
|
|
224
|
+
function getPhoneNumberValidation(value) {
|
|
225
|
+
// Get unique characters that aren't * in the pattern
|
|
226
|
+
const patternNonDelimterCharacters = pattern
|
|
227
|
+
.split("")
|
|
228
|
+
.filter(char => char !== "*")
|
|
229
|
+
.filter((char, index, arr) => arr.indexOf(char) === index);
|
|
230
|
+
const specialCharacters = patternNonDelimterCharacters.join(" ");
|
|
231
|
+
// Remove special characters from pattern
|
|
232
|
+
const cleanValue = value.replace(new RegExp(`[${specialCharacters}]`, "g"), "");
|
|
233
|
+
const cleanValueRequiredLength = (pattern.match(/\*/g) || []).length;
|
|
234
|
+
if (cleanValue.length > 0 && cleanValue.length < cleanValueRequiredLength) {
|
|
235
|
+
return `${errorSubject} must contain ${cleanValueRequiredLength} or more digits`;
|
|
236
|
+
}
|
|
237
|
+
if (typeof (validations === null || validations === void 0 ? void 0 : validations.validate) === "function") {
|
|
238
|
+
return validations.validate(value, getValues);
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isNewInputPhoneNumberProps(props) {
|
|
245
|
+
return props.version === 2;
|
|
246
|
+
}
|
|
247
|
+
const InputPhoneNumber = forwardRef(function InputPhoneNumberShim(props, ref) {
|
|
248
|
+
if (isNewInputPhoneNumberProps(props)) {
|
|
249
|
+
return React__default.createElement(InputPhoneNumberRebuilt, Object.assign({}, props, { ref: ref }));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
return React__default.createElement(InputPhoneNumber$1, Object.assign({}, props));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
export { InputPhoneNumber };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
interface UseInputMaskParams {
|
|
2
|
+
/**
|
|
3
|
+
* The current value of the input
|
|
4
|
+
*/
|
|
5
|
+
value: string;
|
|
6
|
+
/**
|
|
7
|
+
* A pattern to specify the format (e.g. "(***) ***-****")
|
|
8
|
+
*/
|
|
9
|
+
pattern: string;
|
|
10
|
+
/**
|
|
11
|
+
* Character in the pattern that will be replaced with input values
|
|
12
|
+
* @default "*"
|
|
13
|
+
*/
|
|
14
|
+
delimiter?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to restrict input to the pattern length
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
strict?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Callback function to be called with the masked value on change.
|
|
22
|
+
*/
|
|
23
|
+
onChange?: (maskedValue: string, event?: React.ChangeEvent<HTMLInputElement>) => void;
|
|
24
|
+
}
|
|
25
|
+
interface UseInputMaskResult {
|
|
26
|
+
/**
|
|
27
|
+
* The formatted value according to the pattern
|
|
28
|
+
*/
|
|
29
|
+
formattedValue: string;
|
|
30
|
+
/**
|
|
31
|
+
* The masked placeholder text (portion of pattern not yet filled)
|
|
32
|
+
*/
|
|
33
|
+
placeholderMask: string;
|
|
34
|
+
/**
|
|
35
|
+
* Whether masking is currently active
|
|
36
|
+
*/
|
|
37
|
+
isMasking: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Handler for input change events that formats the input and calls the onChange prop
|
|
40
|
+
*/
|
|
41
|
+
maskedOnChange: (newValue: string, event?: React.ChangeEvent<HTMLInputElement>) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Raw input value from the user without formatting characters
|
|
44
|
+
*/
|
|
45
|
+
inputValue: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function useInputMask({ value, pattern, delimiter, strict, onChange, }: UseInputMaskParams): UseInputMaskResult;
|
|
48
|
+
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -66,7 +66,7 @@ var InputFile = require('./InputFile-cjs.js');
|
|
|
66
66
|
var InputGroup = require('./InputGroup-cjs.js');
|
|
67
67
|
var InputNumber = require('./InputNumber-cjs.js');
|
|
68
68
|
var InputPassword = require('./InputPassword-cjs.js');
|
|
69
|
-
var
|
|
69
|
+
var InputPhoneNumber_index = require('./InputPhoneNumber/index.cjs');
|
|
70
70
|
var InputText_index = require('./InputText/index.cjs');
|
|
71
71
|
var InputTime_index = require('./InputTime/index.cjs');
|
|
72
72
|
var InputValidation = require('./InputValidation-cjs.js');
|
|
@@ -290,7 +290,7 @@ exports.useInputFileContentContext = InputFile.useInputFileContentContext;
|
|
|
290
290
|
exports.InputGroup = InputGroup.InputGroup;
|
|
291
291
|
exports.InputNumber = InputNumber.InputNumber;
|
|
292
292
|
exports.InputPassword = InputPassword.InputPassword;
|
|
293
|
-
exports.InputPhoneNumber =
|
|
293
|
+
exports.InputPhoneNumber = InputPhoneNumber_index.InputPhoneNumber;
|
|
294
294
|
exports.InputText = InputText_index.InputText;
|
|
295
295
|
exports.InputTime = InputTime_index.InputTime;
|
|
296
296
|
exports.InputValidation = InputValidation.InputValidation;
|
package/dist/index.mjs
CHANGED
|
@@ -64,7 +64,7 @@ export { I as InputFile, a as InputFileContentContext, u as updateFiles, b as us
|
|
|
64
64
|
export { I as InputGroup } from './InputGroup-es.js';
|
|
65
65
|
export { I as InputNumber } from './InputNumber-es.js';
|
|
66
66
|
export { I as InputPassword } from './InputPassword-es.js';
|
|
67
|
-
export {
|
|
67
|
+
export { InputPhoneNumber } from './InputPhoneNumber/index.mjs';
|
|
68
68
|
export { InputText } from './InputText/index.mjs';
|
|
69
69
|
export { InputTime } from './InputTime/index.mjs';
|
|
70
70
|
export { I as InputValidation } from './InputValidation-es.js';
|