@hh.ru/magritte-ui-pincode-input 1.0.2 → 1.1.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/PincodeInput.d.ts CHANGED
@@ -20,6 +20,14 @@ export interface PincodeInputProps {
20
20
  description?: string;
21
21
  /** Текст сообщения об ошибке */
22
22
  errorMessage?: string;
23
+ /**
24
+ * Если код предполагается отправлять посредством sms то для автозаполнения на Android требуется получение от
25
+ * пользователя разрешения на доступ к sms. Для этого используется WebOTP API. Часть этого API находится в
26
+ * статусе экспериментального и поддерживается далеко не всеми браузерами. В браузерах которые поддерживают это
27
+ * API у пользователя будет запрошено разрешение, поэтому используйте этот флаг только в случае отправки OTP
28
+ * через sms.
29
+ */
30
+ requestCredentials?: boolean;
23
31
  'data-qa'?: string;
24
32
  }
25
33
  export declare const PincodeInput: import("react").ForwardRefExoticComponent<PincodeInputProps & import("react").RefAttributes<HTMLDivElement>>;
package/PincodeInput.js CHANGED
@@ -1,82 +1,110 @@
1
1
  import './index.css';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { forwardRef, useState, useRef } from 'react';
3
+ import { forwardRef, useState, useRef, useLayoutEffect } from 'react';
4
4
  import classnames from 'classnames';
5
- import { keyboardMatch, keyboardKeys } from '@hh.ru/magritte-common-keyboard';
6
- import { Selection } from '@hh.ru/magritte-common-text-selection';
5
+ import { keyboardMatch, keyboardKeys, keyboardMatches } from '@hh.ru/magritte-common-keyboard';
6
+ import { useMultipleRefs } from '@hh.ru/magritte-common-use-multiple-refs';
7
7
  import { FormHelper } from '@hh.ru/magritte-ui-form-helper';
8
+ import { useOTP } from './useOTP.js';
8
9
 
9
- var styles = {"pincode-input":"magritte-pincode-input___RpWyq_1-0-2","pincodeInput":"magritte-pincode-input___RpWyq_1-0-2","medium":"magritte-medium___wfRSc_1-0-2","placeholder":"magritte-placeholder___10kQ0_1-0-2","focus":"magritte-focus___IuhXb_1-0-2","invalid":"magritte-invalid___hB8h2_1-0-2","focus-visible":"magritte-focus-visible___BN1eD_1-0-2","focusVisible":"magritte-focus-visible___BN1eD_1-0-2","digit-box-container":"magritte-digit-box-container___BqMWw_1-0-2","digitBoxContainer":"magritte-digit-box-container___BqMWw_1-0-2","digit-box":"magritte-digit-box___6J0Q2_1-0-2","digitBox":"magritte-digit-box___6J0Q2_1-0-2","digits-input":"magritte-digits-input___4QeLj_1-0-2","digitsInput":"magritte-digits-input___4QeLj_1-0-2","caret-left":"magritte-caret-left___iZMuA_1-0-2","caretLeft":"magritte-caret-left___iZMuA_1-0-2","caret-right":"magritte-caret-right___u8wNk_1-0-2","caretRight":"magritte-caret-right___u8wNk_1-0-2","caret-blink":"magritte-caret-blink___-bhg7_1-0-2","caretBlink":"magritte-caret-blink___-bhg7_1-0-2","value-container":"magritte-value-container___9JR3e_1-0-2","valueContainer":"magritte-value-container___9JR3e_1-0-2"};
10
+ var styles = {"pincode-input":"magritte-pincode-input___RpWyq_1-1-0","pincodeInput":"magritte-pincode-input___RpWyq_1-1-0","medium":"magritte-medium___wfRSc_1-1-0","placeholder":"magritte-placeholder___10kQ0_1-1-0","focus":"magritte-focus___IuhXb_1-1-0","invalid":"magritte-invalid___hB8h2_1-1-0","focus-visible":"magritte-focus-visible___BN1eD_1-1-0","focusVisible":"magritte-focus-visible___BN1eD_1-1-0","digit-box-container":"magritte-digit-box-container___BqMWw_1-1-0","digitBoxContainer":"magritte-digit-box-container___BqMWw_1-1-0","digit-box":"magritte-digit-box___6J0Q2_1-1-0","digitBox":"magritte-digit-box___6J0Q2_1-1-0","digits-input":"magritte-digits-input___4QeLj_1-1-0","digitsInput":"magritte-digits-input___4QeLj_1-1-0","caret-left":"magritte-caret-left___iZMuA_1-1-0","caretLeft":"magritte-caret-left___iZMuA_1-1-0","caret-right":"magritte-caret-right___u8wNk_1-1-0","caretRight":"magritte-caret-right___u8wNk_1-1-0","caret-blink":"magritte-caret-blink___-bhg7_1-1-0","caretBlink":"magritte-caret-blink___-bhg7_1-1-0","value-container":"magritte-value-container___9JR3e_1-1-0","valueContainer":"magritte-value-container___9JR3e_1-1-0","ghost-value":"magritte-ghost-value___jPQwY_1-1-0","ghostValue":"magritte-ghost-value___jPQwY_1-1-0"};
10
11
 
11
- const moveCaretTo = (ref, position, callback) =>
12
- // Каретка сдвигается при изменении содержимого инпута автоматически
13
- // Поэтому двигаем её асинхронно, чтобы сдвиг происходил после нативного, иначе она окажется не в нужной позиции
14
- requestAnimationFrame(() => {
15
- if (ref.current) {
16
- Selection.setCaretPosition(ref.current, position);
17
- callback?.();
18
- }
19
- });
20
- const getCaretPosition = (ref) => ref.current ? (Selection.getCaretPosition(ref.current) ?? 0) : 0;
21
- const PincodeInput = forwardRef(({ digitsCount: _digitsCount = null, size = 'large', invalid = false, onFocus, onBlur, onChange, description, errorMessage, 'data-qa': dataQa = 'magritte-pincode-input', }, ref) => {
12
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
13
+ const PincodeInput = forwardRef(({ digitsCount: _digitsCount = null, size = 'large', invalid = false, onFocus, onBlur, onChange, description, errorMessage, requestCredentials = false, 'data-qa': dataQa = 'magritte-pincode-input', }, ref) => {
22
14
  const digitsCount = _digitsCount ?? (size === 'medium' ? 6 : 4);
23
15
  const [value, setValue] = useState('');
24
16
  const [caretPosition, setCaretPosition] = useState(0);
25
17
  const [focused, setFocused] = useState(false);
26
18
  const [focusVisible, setFocusVisible] = useState(false);
19
+ const [inputKey, setInputKey] = useState(0);
20
+ const ghostLastDigitRef = useRef(null);
21
+ const containerRef = useRef(null);
27
22
  const inputRef = useRef(null);
28
- const selectionRef = useRef(null);
23
+ const autofillValueRef = useRef('');
29
24
  const updateValue = (newValue) => {
30
25
  setValue(newValue);
31
26
  onChange?.(newValue);
32
27
  };
33
- const refineCaretPosition = () => {
34
- // если попытаться получить позицию каретки синхронно, то получим позицию до изменения
35
- requestAnimationFrame(() => {
36
- if (!inputRef.current) {
37
- return;
38
- }
39
- // Когда перемещаемся между цифрами если в позиции введена цифра, то ввод должен заменят именно её
40
- // а не сдвигать весь текст после нее врпаво, как в обычном инпуте.
41
- // Поэтому здесь проверяем если в позиции что-то введено, то не просто устанавливаем каретку, а выделяем
42
- // символ, чтобы при вводе он заменился
43
- const caretPosition = getCaretPosition(inputRef);
44
- const value = inputRef.current?.value ?? '';
45
- const digitNumberToSelect = Math.min(caretPosition, Math.min(value.length, digitsCount - 1));
46
- const selection = Selection.get(inputRef.current);
47
- if (selectionRef.current && selection.start !== selection.end) {
48
- return;
49
- }
50
- selectionRef.current = null;
51
- if (value.length > digitNumberToSelect) {
52
- Selection.set(inputRef.current, digitNumberToSelect, digitNumberToSelect + 1, 'forward');
53
- selectionRef.current = digitNumberToSelect;
54
- }
55
- setCaretPosition(caretPosition);
56
- });
57
- };
58
- return (jsxs("div", { ref: ref, "data-qa": dataQa, children: [jsxs("div", { "data-qa": "magritte-pincode-input-wrapper", className: classnames(styles.pincodeInput, {
28
+ useOTP((OPTCode) => {
29
+ if (!OPTCode) {
30
+ return;
31
+ }
32
+ updateValue(OPTCode);
33
+ setCaretPosition(OPTCode.length - 1);
34
+ setInputKey((key) => key + 1);
35
+ }, requestCredentials);
36
+ // внутри этого хука безопасно не мемоизировать рефколлбек, коллбек который будет возвращен будет мемоизирован
37
+ // и не будет меняться при изменении ссылок на аргументы
38
+ const inputRefMultiplexer = useMultipleRefs(inputRef, (element) => {
39
+ if (element && focused) {
40
+ element.focus();
41
+ }
42
+ });
43
+ useLayoutEffect(() => {
44
+ if (!containerRef.current || !ghostLastDigitRef.current) {
45
+ return;
46
+ }
47
+ const lastWidth = ghostLastDigitRef.current.getBoundingClientRect().width;
48
+ containerRef.current.style.setProperty('--magritte-pincode-last-digit-width', `${lastWidth}px`);
49
+ }, [caretPosition, value]);
50
+ const caretPos = clamp(caretPosition, 0, value.length);
51
+ return (jsxs("div", { ref: ref, "data-qa": dataQa, tabIndex: 0, onFocus: () => inputRef.current?.focus(), children: [jsxs("div", { "data-qa": "magritte-pincode-input-wrapper", ref: containerRef, className: classnames(styles.pincodeInput, {
59
52
  [styles.focus]: focused,
60
53
  [styles.focusVisible]: focusVisible,
61
54
  [styles.medium]: size === 'medium',
62
55
  [styles.invalid]: invalid,
63
- }), onClick: (event) => {
56
+ }), onMouseDown: (event) => {
57
+ if (event.target === inputRef.current) {
58
+ return;
59
+ }
60
+ event.preventDefault();
64
61
  const caretPosition = Math.min(Number(event.target?.dataset?.digitIndex ?? value.length), value.length);
65
- inputRef.current?.focus();
66
- moveCaretTo(inputRef, caretPosition);
67
- refineCaretPosition();
68
- },
69
- // Этот обработчик нужен для того, чтобы не сбрасывался фокус при клике на дочерние элементы
70
- // Визуально это проявляется как моргание цвета рамки если клик происходит когда инпут в фокусе
71
- onMouseDown: (event) => event.preventDefault(), style: { '--magritte-pincode-digits-count': digitsCount }, children: [jsx("input", { ref: inputRef, autoComplete: "one-time-code", pattern: `\\d{${digitsCount}}`, "data-qa": "magritte-pincode-input-field", className: styles.digitsInput, type: "text", inputMode: "numeric", maxLength: digitsCount, value: value, onFocus: (event) => {
62
+ if (!focused) {
63
+ inputRef.current?.focus();
64
+ }
65
+ setCaretPosition(caretPosition);
66
+ }, style: {
67
+ '--magritte-pincode-digits-count': digitsCount,
68
+ '--magritte-pincode-caret-position': caretPos,
69
+ }, children: [jsx("input", { ref: inputRefMultiplexer, tabIndex: -1, autoComplete: "one-time-code", pattern: `\\d{${digitsCount}}`, "data-qa": "magritte-pincode-input-field", className: styles.digitsInput, type: "text", inputMode: "numeric", maxLength: digitsCount,
70
+ // оставляем инпут всегда пустым, т.к. он нам нужен только для показа контекстного меню вставки,
71
+ // обработки события вставки и обработки фокуса
72
+ value: "", onChange: (event) => {
73
+ if (!/\d/.test(event.target.value)) {
74
+ return;
75
+ }
76
+ // Вставку в инпут и нажатия клавиш цифр мы перехватываем
77
+ // поэтому если попали сюда, значит использовался autofill (например из sms).
78
+ // На iOS autofill генерит несколько событий, по количеству заполняемых символов.
79
+ // Мы не меняем значение самого инпута, поэтому каждое событие будет содержать только
80
+ // один символ. Сохраняем их и когда их количество достигнет ожидаемого (digitsCount)
81
+ // подставляем в значение
82
+ autofillValueRef.current += event.target.value;
83
+ if (autofillValueRef.current.length >= digitsCount) {
84
+ updateValue(autofillValueRef.current.slice(0, digitsCount));
85
+ autofillValueRef.current = '';
86
+ setCaretPosition(digitsCount - 1);
87
+ // Кроме того при автозаполнении инпут красится в желтый цвет, что в нашем случае
88
+ // недопустимо т.к. инпут располагается поверх цифр и должен быть прозрачным. Это нельзя
89
+ // переопределить с помощью css, есть только хак когда задают transition для цвета фона
90
+ // это замедлит смену цвета, но в конечном итоге инпут все же поменяет цвет фона.
91
+ // Поэтому меняем key у инпута, чтобы он перемонтировался и браузер не определял его как
92
+ // автозаполненный инпут. Важно после перемонтирования не забыть вернуть в него фокус,
93
+ // иначе для вызова меню вставки потребуется два тапа вместо одного.
94
+ setInputKey((key) => key + 1);
95
+ }
96
+ }, onFocus: (event) => {
97
+ if (focused) {
98
+ return;
99
+ }
72
100
  onFocus?.(event);
73
- refineCaretPosition();
74
101
  if (!event.defaultPrevented) {
75
102
  setFocused(true);
76
103
  setFocusVisible(!!inputRef.current?.classList.contains('focus-visible'));
77
104
  if (invalid) {
78
105
  // По спеке если инпут помечен как invalid при фокусе содержимое должно очищаться
79
106
  updateValue('');
107
+ setCaretPosition(0);
80
108
  }
81
109
  }
82
110
  }, onBlur: (event) => {
@@ -90,73 +118,64 @@ const PincodeInput = forwardRef(({ digitsCount: _digitsCount = null, size = 'lar
90
118
  return;
91
119
  }
92
120
  const isLeft = keyboardMatch(event, keyboardKeys.ArrowLeft);
93
- // Если одна из цифр выделена, и мы пытаемся передвинут каретку влево с помощью клавиатуры,
94
- // нужно делать это вручную, так как цифры мы выделяем справо на лево, и при нативном
95
- // перемещении курсо просто встанет перед выделенной цифрой, а refineCaretPosition yсова её
96
- // выделит и поставит каретку в исходное положение. Т.е. без этого кода не получится
97
- // передвинуть каретку влево, если выделена цифра
98
- if (isLeft && selectionRef.current !== null) {
121
+ const isRight = keyboardMatch(event, keyboardKeys.ArrowRight);
122
+ if (isLeft || isRight) {
99
123
  event.preventDefault();
100
- moveCaretTo(inputRef, Math.max(0, Math.min(selectionRef.current - 1, digitsCount - 1)), refineCaretPosition);
124
+ setCaretPosition((caretPosition) => clamp(caretPosition + (isRight ? 1 : -1), 0, value.length));
101
125
  return;
102
126
  }
103
- if (keyboardMatch(event, keyboardKeys.Backspace) && selectionRef.current !== null) {
127
+ if (keyboardMatches(event, keyboardKeys.DigitKeys)) {
128
+ updateValue(`${value.slice(0, caretPos)}${event.key}${value.slice(caretPos + 1)}`);
129
+ setCaretPosition((caretPosition) => clamp(caretPosition + 1, 0, digitsCount - 1));
130
+ event.preventDefault();
131
+ return;
132
+ }
133
+ const isBackspace = keyboardMatch(event, keyboardKeys.Backspace);
134
+ const isDelete = keyboardMatch(event, keyboardKeys.Delete);
135
+ if (isBackspace || isDelete) {
104
136
  // При нажатии backspace действует следующая логика:
105
137
  // Если каретка находится в позиции последней введенной цифры, то удаляем её
106
138
  // и оставляем каретку на той же позиции
107
139
  // Если каретка находится в другой позиции, то удаляем цифру слева от каретки и
108
140
  // сдвигаем каретку влево
141
+ const isLastDigit = caretPos === value.length - 1;
142
+ const deleteIndex = clamp(caretPos - (isDelete || isLastDigit ? 0 : 1), 0, digitsCount - 1);
143
+ updateValue(`${value.slice(0, deleteIndex)}${value.slice(deleteIndex + 1)}`);
144
+ setCaretPosition((caretPosition) => {
145
+ const newPosition = isLastDigit || isDelete ? caretPosition : Math.max(caretPosition - 1, 0);
146
+ return newPosition;
147
+ });
109
148
  event.preventDefault();
110
- const deleteIndex = selectionRef.current === value.length - 1
111
- ? selectionRef.current
112
- : Math.max(selectionRef.current - 1, 0);
113
- const newValue = `${value.slice(0, deleteIndex)}${value.slice(deleteIndex + 1)}`;
114
- updateValue(newValue);
115
- if (selectionRef.current !== value.length - 1) {
116
- moveCaretTo(inputRef, Math.max(selectionRef.current - 1, 0), refineCaretPosition);
117
- return;
118
- }
119
- }
120
- refineCaretPosition();
121
- }, onChange: (event) => {
122
- updateValue(event.target.value);
123
- if (selectionRef.current !== null) {
124
- // При изменении, если была выделена цифра, то каретку надо сдвинуть на следующую
125
- // позицию вручную
126
- moveCaretTo(inputRef, selectionRef.current + 1, refineCaretPosition);
127
- return;
128
149
  }
129
- refineCaretPosition();
130
150
  }, onPaste: (event) => {
131
151
  event.preventDefault();
132
152
  const pasteValue = event.clipboardData.getData('text').replace(/\D/g, '');
133
153
  if (pasteValue.length === digitsCount) {
134
154
  updateValue(pasteValue);
135
- moveCaretTo(inputRef, digitsCount, refineCaretPosition);
155
+ setCaretPosition(digitsCount - 1);
136
156
  return;
137
157
  }
138
- const caretPosition = Math.min(getCaretPosition(inputRef), selectionRef.current ?? value.length);
139
- const newValue = `${value.slice(0, caretPosition)}${pasteValue}${value.slice(caretPosition + pasteValue.length)}`.slice(0, digitsCount);
158
+ const newValue = `${value.slice(0, caretPos)}${pasteValue}${value.slice(caretPos + pasteValue.length)}`.slice(0, digitsCount);
140
159
  updateValue(newValue);
141
- moveCaretTo(inputRef, caretPosition + pasteValue.length, refineCaretPosition);
142
- } }), jsx("div", { className: styles.digitBoxContainer, children: Array.from({ length: digitsCount }).map((_, index) => (jsx("div", { className: classnames(styles.digitBox), "data-digit-index": index, children: jsx("span", { className: classnames(styles.valueContainer, {
160
+ setCaretPosition(Math.min(digitsCount - 1, caretPos + pasteValue.length));
161
+ } }, inputKey), jsx("div", { className: styles.digitBoxContainer, children: Array.from({ length: digitsCount }).map((_, index) => (jsx("div", { className: classnames(styles.digitBox), "data-digit-index": index, children: jsx("span", { className: classnames(styles.valueContainer, {
143
162
  // в позицию ничего не введено, и каретка не находится в этой позиции
144
163
  [styles.placeholder]: index >= value.length &&
145
164
  (!focused ||
146
- (index !== caretPosition &&
147
- !(caretPosition === digitsCount && index === digitsCount - 1))),
165
+ (index !== caretPos &&
166
+ !(caretPos === digitsCount && index === digitsCount - 1))),
148
167
  // в позицию введена цифра, каретка находится в этой позиции, фокус
149
168
  // установлен в инпут, это не крайняя справа цифра из введенных
150
- [styles.caretLeft]: index === caretPosition && index !== value.length - 1 && focused,
169
+ [styles.caretLeft]: index === caretPos && index !== value.length - 1 && focused,
151
170
  // в позицию введена цифра, каретка находится в этой позиции, либо в следующей
152
171
  // если введены все цифры (потому что при вводе последней цифры каретка
153
172
  // должна уходить за нее), фокус установлен в инпут, это крайняя справа цифра
154
173
  // из введенных
155
- [styles.caretRight]: (index === caretPosition ||
156
- (index === digitsCount - 1 && index === caretPosition - 1)) &&
174
+ [styles.caretRight]: (index === caretPos ||
175
+ (index === digitsCount - 1 && index === caretPos - 1)) &&
157
176
  index === value.length - 1 &&
158
177
  focused,
159
- }), "data-qa": `magritte-pincode-input-digit-${index}`, children: value[index] || (caretPosition === index && focused ? '' : '•') }) }, index))) })] }), jsx(FormHelper, { invalid: invalid, description: description, errorMessage: errorMessage, "data-qa": "magritte-pincode-input-message" })] }));
178
+ }), "data-qa": `magritte-pincode-input-digit-${index}`, children: value[index] || (caretPos === index && focused ? '' : '•') }) }, index))) }), jsx("div", { className: styles.ghostValue, ref: ghostLastDigitRef, children: value.slice(caretPos, caretPos + 1) })] }), jsx(FormHelper, { invalid: invalid, description: description, errorMessage: errorMessage, "data-qa": "magritte-pincode-input-message" })] }));
160
179
  });
161
180
  PincodeInput.displayName = 'PincodeInput';
162
181
 
@@ -1 +1 @@
1
- {"version":3,"file":"PincodeInput.js","sources":["../src/PincodeInput.tsx"],"sourcesContent":["import { type CSSProperties, type FocusEventHandler, useRef, useState, RefObject, forwardRef } from 'react';\nimport classnames from 'classnames';\n\nimport { keyboardKeys, keyboardMatch } from '@hh.ru/magritte-common-keyboard';\nimport { Selection } from '@hh.ru/magritte-common-text-selection';\nimport { FormHelper } from '@hh.ru/magritte-ui-form-helper';\n\nimport styles from './pincodeInput.less';\n\nconst moveCaretTo = (ref: RefObject<HTMLInputElement>, position: number, callback?: VoidFunction) =>\n // Каретка сдвигается при изменении содержимого инпута автоматически\n // Поэтому двигаем её асинхронно, чтобы сдвиг происходил после нативного, иначе она окажется не в нужной позиции\n requestAnimationFrame(() => {\n if (ref.current) {\n Selection.setCaretPosition(ref.current, position);\n callback?.();\n }\n });\n\nconst getCaretPosition = (ref: RefObject<HTMLInputElement>) =>\n ref.current ? (Selection.getCaretPosition(ref.current) ?? 0) : 0;\n\nexport type PincodeInputSize = 'medium' | 'large';\n\nexport interface PincodeInputProps {\n /**\n * Количество цифр в пин-коде, если не указано будет выбрано автоматически\n * в зависимости от размера инпута ( medium - 6, large - 4)\n */\n digitsCount?: number | null;\n /** Размер инпута */\n size?: PincodeInputSize;\n /** Обработчик изменений */\n onChange?: (value: string) => void;\n /** Обработчик фокуса */\n onFocus?: FocusEventHandler<HTMLInputElement>;\n /** Обработчик потери фокуса */\n onBlur?: FocusEventHandler<HTMLInputElement>;\n /** Флаг наличия ошибки */\n invalid?: boolean;\n /** Текст описания */\n description?: string;\n /** Текст сообщения об ошибке */\n errorMessage?: string;\n 'data-qa'?: string;\n}\n\nexport const PincodeInput = forwardRef<HTMLDivElement, PincodeInputProps>(\n (\n {\n digitsCount: _digitsCount = null,\n size = 'large',\n invalid = false,\n onFocus,\n onBlur,\n onChange,\n description,\n errorMessage,\n 'data-qa': dataQa = 'magritte-pincode-input',\n },\n ref\n ) => {\n const digitsCount = _digitsCount ?? (size === 'medium' ? 6 : 4);\n const [value, setValue] = useState<string>('');\n const [caretPosition, setCaretPosition] = useState<number>(0);\n const [focused, setFocused] = useState(false);\n const [focusVisible, setFocusVisible] = useState(false);\n\n const inputRef = useRef<HTMLInputElement>(null);\n const selectionRef = useRef<number | null>(null);\n\n const updateValue = (newValue: string) => {\n setValue(newValue);\n onChange?.(newValue);\n };\n\n const refineCaretPosition = () => {\n // если попытаться получить позицию каретки синхронно, то получим позицию до изменения\n requestAnimationFrame(() => {\n if (!inputRef.current) {\n return;\n }\n // Когда перемещаемся между цифрами если в позиции введена цифра, то ввод должен заменят именно её\n // а не сдвигать весь текст после нее врпаво, как в обычном инпуте.\n // Поэтому здесь проверяем если в позиции что-то введено, то не просто устанавливаем каретку, а выделяем\n // символ, чтобы при вводе он заменился\n const caretPosition = getCaretPosition(inputRef);\n const value = inputRef.current?.value ?? '';\n const digitNumberToSelect = Math.min(caretPosition, Math.min(value.length, digitsCount - 1));\n const selection = Selection.get(inputRef.current);\n if (selectionRef.current && selection.start !== selection.end) {\n return;\n }\n selectionRef.current = null;\n if (value.length > digitNumberToSelect) {\n Selection.set(inputRef.current, digitNumberToSelect, digitNumberToSelect + 1, 'forward');\n selectionRef.current = digitNumberToSelect;\n }\n setCaretPosition(caretPosition);\n });\n };\n\n return (\n <div ref={ref} data-qa={dataQa}>\n <div\n data-qa=\"magritte-pincode-input-wrapper\"\n className={classnames(styles.pincodeInput, {\n [styles.focus]: focused,\n [styles.focusVisible]: focusVisible,\n [styles.medium]: size === 'medium',\n [styles.invalid]: invalid,\n })}\n onClick={(event) => {\n const caretPosition = Math.min(\n Number((event.target as HTMLElement)?.dataset?.digitIndex ?? value.length),\n value.length\n );\n inputRef.current?.focus();\n moveCaretTo(inputRef, caretPosition);\n refineCaretPosition();\n }}\n // Этот обработчик нужен для того, чтобы не сбрасывался фокус при клике на дочерние элементы\n // Визуально это проявляется как моргание цвета рамки если клик происходит когда инпут в фокусе\n onMouseDown={(event) => event.preventDefault()}\n style={{ '--magritte-pincode-digits-count': digitsCount } as CSSProperties}\n >\n <input\n ref={inputRef}\n autoComplete=\"one-time-code\"\n pattern={`\\\\d{${digitsCount}}`}\n data-qa=\"magritte-pincode-input-field\"\n className={styles.digitsInput}\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={digitsCount}\n value={value}\n onFocus={(event) => {\n onFocus?.(event);\n refineCaretPosition();\n if (!event.defaultPrevented) {\n setFocused(true);\n setFocusVisible(!!inputRef.current?.classList.contains('focus-visible'));\n if (invalid) {\n // По спеке если инпут помечен как invalid при фокусе содержимое должно очищаться\n updateValue('');\n }\n }\n }}\n onBlur={(event) => {\n onBlur?.(event);\n if (!event.defaultPrevented) {\n setFocused(false);\n setFocusVisible(false);\n }\n }}\n onKeyDown={(event) => {\n if (!inputRef.current) {\n return;\n }\n\n const isLeft = keyboardMatch(event, keyboardKeys.ArrowLeft);\n\n // Если одна из цифр выделена, и мы пытаемся передвинут каретку влево с помощью клавиатуры,\n // нужно делать это вручную, так как цифры мы выделяем справо на лево, и при нативном\n // перемещении курсо просто встанет перед выделенной цифрой, а refineCaretPosition yсова её\n // выделит и поставит каретку в исходное положение. Т.е. без этого кода не получится\n // передвинуть каретку влево, если выделена цифра\n if (isLeft && selectionRef.current !== null) {\n event.preventDefault();\n moveCaretTo(\n inputRef,\n Math.max(0, Math.min(selectionRef.current - 1, digitsCount - 1)),\n refineCaretPosition\n );\n return;\n }\n\n if (keyboardMatch(event, keyboardKeys.Backspace) && selectionRef.current !== null) {\n // При нажатии backspace действует следующая логика:\n // Если каретка находится в позиции последней введенной цифры, то удаляем её\n // и оставляем каретку на той же позиции\n // Если каретка находится в другой позиции, то удаляем цифру слева от каретки и\n // сдвигаем каретку влево\n event.preventDefault();\n const deleteIndex =\n selectionRef.current === value.length - 1\n ? selectionRef.current\n : Math.max(selectionRef.current - 1, 0);\n const newValue = `${value.slice(0, deleteIndex)}${value.slice(deleteIndex + 1)}`;\n updateValue(newValue);\n if (selectionRef.current !== value.length - 1) {\n moveCaretTo(inputRef, Math.max(selectionRef.current - 1, 0), refineCaretPosition);\n return;\n }\n }\n\n refineCaretPosition();\n }}\n onChange={(event) => {\n updateValue(event.target.value);\n if (selectionRef.current !== null) {\n // При изменении, если была выделена цифра, то каретку надо сдвинуть на следующую\n // позицию вручную\n moveCaretTo(inputRef, selectionRef.current + 1, refineCaretPosition);\n return;\n }\n refineCaretPosition();\n }}\n onPaste={(event) => {\n event.preventDefault();\n const pasteValue = event.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasteValue.length === digitsCount) {\n updateValue(pasteValue);\n moveCaretTo(inputRef, digitsCount, refineCaretPosition);\n return;\n }\n\n const caretPosition = Math.min(\n getCaretPosition(inputRef),\n selectionRef.current ?? value.length\n );\n const newValue =\n `${value.slice(0, caretPosition)}${pasteValue}${value.slice(caretPosition + pasteValue.length)}`.slice(\n 0,\n digitsCount\n );\n updateValue(newValue);\n moveCaretTo(inputRef, caretPosition + pasteValue.length, refineCaretPosition);\n }}\n />\n <div className={styles.digitBoxContainer}>\n {/* eslint-disable-next-line no-restricted-properties */}\n {Array.from({ length: digitsCount }).map((_, index) => (\n <div key={index} className={classnames(styles.digitBox)} data-digit-index={index}>\n <span\n className={classnames(styles.valueContainer, {\n // в позицию ничего не введено, и каретка не находится в этой позиции\n [styles.placeholder]:\n index >= value.length &&\n (!focused ||\n (index !== caretPosition &&\n !(caretPosition === digitsCount && index === digitsCount - 1))),\n // в позицию введена цифра, каретка находится в этой позиции, фокус\n // установлен в инпут, это не крайняя справа цифра из введенных\n [styles.caretLeft]:\n index === caretPosition && index !== value.length - 1 && focused,\n // в позицию введена цифра, каретка находится в этой позиции, либо в следующей\n // если введены все цифры (потому что при вводе последней цифры каретка\n // должна уходить за нее), фокус установлен в инпут, это крайняя справа цифра\n // из введенных\n [styles.caretRight]:\n (index === caretPosition ||\n (index === digitsCount - 1 && index === caretPosition - 1)) &&\n index === value.length - 1 &&\n focused,\n })}\n data-qa={`magritte-pincode-input-digit-${index}`}\n >\n {value[index] || (caretPosition === index && focused ? '' : '•')}\n </span>\n </div>\n ))}\n </div>\n </div>\n <FormHelper\n invalid={invalid}\n description={description}\n errorMessage={errorMessage}\n data-qa=\"magritte-pincode-input-message\"\n />\n </div>\n );\n }\n);\n\nPincodeInput.displayName = 'PincodeInput';\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"PincodeInput.js","sources":["../src/PincodeInput.tsx"],"sourcesContent":["import { type CSSProperties, type FocusEventHandler, useRef, useState, forwardRef, useLayoutEffect } from 'react';\nimport classnames from 'classnames';\n\nimport { keyboardKeys, keyboardMatch, keyboardMatches } from '@hh.ru/magritte-common-keyboard';\nimport { useMultipleRefs } from '@hh.ru/magritte-common-use-multiple-refs';\nimport { FormHelper } from '@hh.ru/magritte-ui-form-helper';\nimport { useOTP } from '@hh.ru/magritte-ui-pincode-input/useOTP';\n\nimport styles from './pincodeInput.less';\n\nexport type PincodeInputSize = 'medium' | 'large';\n\nexport interface PincodeInputProps {\n /**\n * Количество цифр в пин-коде, если не указано будет выбрано автоматически\n * в зависимости от размера инпута ( medium - 6, large - 4)\n */\n digitsCount?: number | null;\n /** Размер инпута */\n size?: PincodeInputSize;\n /** Обработчик изменений */\n onChange?: (value: string) => void;\n /** Обработчик фокуса */\n onFocus?: FocusEventHandler<HTMLInputElement>;\n /** Обработчик потери фокуса */\n onBlur?: FocusEventHandler<HTMLInputElement>;\n /** Флаг наличия ошибки */\n invalid?: boolean;\n /** Текст описания */\n description?: string;\n /** Текст сообщения об ошибке */\n errorMessage?: string;\n /**\n * Если код предполагается отправлять посредством sms то для автозаполнения на Android требуется получение от\n * пользователя разрешения на доступ к sms. Для этого используется WebOTP API. Часть этого API находится в\n * статусе экспериментального и поддерживается далеко не всеми браузерами. В браузерах которые поддерживают это\n * API у пользователя будет запрошено разрешение, поэтому используйте этот флаг только в случае отправки OTP\n * через sms.\n */\n requestCredentials?: boolean;\n 'data-qa'?: string;\n}\n\nconst clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);\n\nexport const PincodeInput = forwardRef<HTMLDivElement, PincodeInputProps>(\n (\n {\n digitsCount: _digitsCount = null,\n size = 'large',\n invalid = false,\n onFocus,\n onBlur,\n onChange,\n description,\n errorMessage,\n requestCredentials = false,\n 'data-qa': dataQa = 'magritte-pincode-input',\n },\n ref\n ) => {\n const digitsCount = _digitsCount ?? (size === 'medium' ? 6 : 4);\n const [value, setValue] = useState<string>('');\n const [caretPosition, setCaretPosition] = useState<number>(0);\n const [focused, setFocused] = useState(false);\n const [focusVisible, setFocusVisible] = useState(false);\n const [inputKey, setInputKey] = useState(0);\n\n const ghostLastDigitRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const autofillValueRef = useRef('');\n\n const updateValue = (newValue: string) => {\n setValue(newValue);\n onChange?.(newValue);\n };\n\n useOTP((OPTCode) => {\n if (!OPTCode) {\n return;\n }\n updateValue(OPTCode);\n setCaretPosition(OPTCode.length - 1);\n setInputKey((key) => key + 1);\n }, requestCredentials);\n\n // внутри этого хука безопасно не мемоизировать рефколлбек, коллбек который будет возвращен будет мемоизирован\n // и не будет меняться при изменении ссылок на аргументы\n const inputRefMultiplexer = useMultipleRefs(inputRef, (element) => {\n if (element && focused) {\n element.focus();\n }\n });\n\n useLayoutEffect(() => {\n if (!containerRef.current || !ghostLastDigitRef.current) {\n return;\n }\n\n const lastWidth = ghostLastDigitRef.current.getBoundingClientRect().width;\n containerRef.current.style.setProperty('--magritte-pincode-last-digit-width', `${lastWidth}px`);\n }, [caretPosition, value]);\n\n const caretPos = clamp(caretPosition, 0, value.length);\n\n return (\n <div ref={ref} data-qa={dataQa} tabIndex={0} onFocus={() => inputRef.current?.focus()}>\n <div\n data-qa=\"magritte-pincode-input-wrapper\"\n ref={containerRef}\n className={classnames(styles.pincodeInput, {\n [styles.focus]: focused,\n [styles.focusVisible]: focusVisible,\n [styles.medium]: size === 'medium',\n [styles.invalid]: invalid,\n })}\n onMouseDown={(event) => {\n if (event.target === inputRef.current) {\n return;\n }\n event.preventDefault();\n\n const caretPosition = Math.min(\n Number((event.target as HTMLElement)?.dataset?.digitIndex ?? value.length),\n value.length\n );\n\n if (!focused) {\n inputRef.current?.focus();\n }\n\n setCaretPosition(caretPosition);\n }}\n style={\n {\n '--magritte-pincode-digits-count': digitsCount,\n '--magritte-pincode-caret-position': caretPos,\n } as CSSProperties\n }\n >\n <input\n key={inputKey}\n ref={inputRefMultiplexer}\n tabIndex={-1}\n autoComplete=\"one-time-code\"\n pattern={`\\\\d{${digitsCount}}`}\n data-qa=\"magritte-pincode-input-field\"\n className={styles.digitsInput}\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={digitsCount}\n // оставляем инпут всегда пустым, т.к. он нам нужен только для показа контекстного меню вставки,\n // обработки события вставки и обработки фокуса\n value=\"\"\n onChange={(event) => {\n if (!/\\d/.test(event.target.value)) {\n return;\n }\n\n // Вставку в инпут и нажатия клавиш цифр мы перехватываем\n // поэтому если попали сюда, значит использовался autofill (например из sms).\n // На iOS autofill генерит несколько событий, по количеству заполняемых символов.\n // Мы не меняем значение самого инпута, поэтому каждое событие будет содержать только\n // один символ. Сохраняем их и когда их количество достигнет ожидаемого (digitsCount)\n // подставляем в значение\n autofillValueRef.current += event.target.value;\n\n if (autofillValueRef.current.length >= digitsCount) {\n updateValue(autofillValueRef.current.slice(0, digitsCount));\n autofillValueRef.current = '';\n setCaretPosition(digitsCount - 1);\n\n // Кроме того при автозаполнении инпут красится в желтый цвет, что в нашем случае\n // недопустимо т.к. инпут располагается поверх цифр и должен быть прозрачным. Это нельзя\n // переопределить с помощью css, есть только хак когда задают transition для цвета фона\n // это замедлит смену цвета, но в конечном итоге инпут все же поменяет цвет фона.\n // Поэтому меняем key у инпута, чтобы он перемонтировался и браузер не определял его как\n // автозаполненный инпут. Важно после перемонтирования не забыть вернуть в него фокус,\n // иначе для вызова меню вставки потребуется два тапа вместо одного.\n setInputKey((key) => key + 1);\n }\n }}\n onFocus={(event) => {\n if (focused) {\n return;\n }\n onFocus?.(event);\n if (!event.defaultPrevented) {\n setFocused(true);\n setFocusVisible(!!inputRef.current?.classList.contains('focus-visible'));\n if (invalid) {\n // По спеке если инпут помечен как invalid при фокусе содержимое должно очищаться\n updateValue('');\n setCaretPosition(0);\n }\n }\n }}\n onBlur={(event) => {\n onBlur?.(event);\n if (!event.defaultPrevented) {\n setFocused(false);\n setFocusVisible(false);\n }\n }}\n onKeyDown={(event) => {\n if (!inputRef.current) {\n return;\n }\n\n const isLeft = keyboardMatch(event, keyboardKeys.ArrowLeft);\n const isRight = keyboardMatch(event, keyboardKeys.ArrowRight);\n if (isLeft || isRight) {\n event.preventDefault();\n setCaretPosition((caretPosition) =>\n clamp(caretPosition + (isRight ? 1 : -1), 0, value.length)\n );\n return;\n }\n\n if (keyboardMatches(event, keyboardKeys.DigitKeys)) {\n updateValue(`${value.slice(0, caretPos)}${event.key}${value.slice(caretPos + 1)}`);\n setCaretPosition((caretPosition) => clamp(caretPosition + 1, 0, digitsCount - 1));\n event.preventDefault();\n return;\n }\n\n const isBackspace = keyboardMatch(event, keyboardKeys.Backspace);\n const isDelete = keyboardMatch(event, keyboardKeys.Delete);\n\n if (isBackspace || isDelete) {\n // При нажатии backspace действует следующая логика:\n // Если каретка находится в позиции последней введенной цифры, то удаляем её\n // и оставляем каретку на той же позиции\n // Если каретка находится в другой позиции, то удаляем цифру слева от каретки и\n // сдвигаем каретку влево\n\n const isLastDigit = caretPos === value.length - 1;\n const deleteIndex = clamp(\n caretPos - (isDelete || isLastDigit ? 0 : 1),\n 0,\n digitsCount - 1\n );\n updateValue(`${value.slice(0, deleteIndex)}${value.slice(deleteIndex + 1)}`);\n setCaretPosition((caretPosition) => {\n const newPosition =\n isLastDigit || isDelete ? caretPosition : Math.max(caretPosition - 1, 0);\n\n return newPosition;\n });\n\n event.preventDefault();\n }\n }}\n onPaste={(event) => {\n event.preventDefault();\n const pasteValue = event.clipboardData.getData('text').replace(/\\D/g, '');\n if (pasteValue.length === digitsCount) {\n updateValue(pasteValue);\n setCaretPosition(digitsCount - 1);\n return;\n }\n\n const newValue =\n `${value.slice(0, caretPos)}${pasteValue}${value.slice(caretPos + pasteValue.length)}`.slice(\n 0,\n digitsCount\n );\n updateValue(newValue);\n setCaretPosition(Math.min(digitsCount - 1, caretPos + pasteValue.length));\n }}\n />\n <div className={styles.digitBoxContainer}>\n {/* eslint-disable-next-line no-restricted-properties */}\n {Array.from({ length: digitsCount }).map((_, index) => (\n <div key={index} className={classnames(styles.digitBox)} data-digit-index={index}>\n <span\n className={classnames(styles.valueContainer, {\n // в позицию ничего не введено, и каретка не находится в этой позиции\n [styles.placeholder]:\n index >= value.length &&\n (!focused ||\n (index !== caretPos &&\n !(caretPos === digitsCount && index === digitsCount - 1))),\n // в позицию введена цифра, каретка находится в этой позиции, фокус\n // установлен в инпут, это не крайняя справа цифра из введенных\n [styles.caretLeft]: index === caretPos && index !== value.length - 1 && focused,\n // в позицию введена цифра, каретка находится в этой позиции, либо в следующей\n // если введены все цифры (потому что при вводе последней цифры каретка\n // должна уходить за нее), фокус установлен в инпут, это крайняя справа цифра\n // из введенных\n [styles.caretRight]:\n (index === caretPos ||\n (index === digitsCount - 1 && index === caretPos - 1)) &&\n index === value.length - 1 &&\n focused,\n })}\n data-qa={`magritte-pincode-input-digit-${index}`}\n >\n {value[index] || (caretPos === index && focused ? '' : '•')}\n </span>\n </div>\n ))}\n </div>\n <div className={styles.ghostValue} ref={ghostLastDigitRef}>\n {value.slice(caretPos, caretPos + 1)}\n </div>\n </div>\n <FormHelper\n invalid={invalid}\n description={description}\n errorMessage={errorMessage}\n data-qa=\"magritte-pincode-input-message\"\n />\n </div>\n );\n }\n);\n\nPincodeInput.displayName = 'PincodeInput';\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
package/index.css CHANGED
@@ -4,6 +4,7 @@
4
4
  --magritte-color-component-input-stroke-state-field-focused-v21-1-0:#0070ff;
5
5
  --magritte-color-component-input-stroke-state-accent-focused-accessible-v21-1-0:#0070ff7a;
6
6
  --magritte-color-component-input-stroke-state-field-invalid-v21-1-0:#ff4d3a;
7
+ --magritte-color-component-input-stroke-state-negative-focused-accessible-v21-1-0:#ff4d3a7a;
7
8
  --magritte-color-component-input-background-field-v21-1-0:#ffffff;
8
9
  --magritte-color-component-input-background-state-field-hovered-v21-1-0:#ffffff;
9
10
  --magritte-color-component-input-background-state-field-focused-v21-1-0:#ffffff;
@@ -28,6 +29,7 @@
28
29
  --magritte-color-component-input-stroke-state-field-focused-v21-1-0:#0070ff;
29
30
  --magritte-color-component-input-stroke-state-accent-focused-accessible-v21-1-0:#0070ff7a;
30
31
  --magritte-color-component-input-stroke-state-field-invalid-v21-1-0:#ff4d3a;
32
+ --magritte-color-component-input-stroke-state-negative-focused-accessible-v21-1-0:#ff4d3a7a;
31
33
  --magritte-color-component-input-background-field-v21-1-0:#1B1B1B;
32
34
  --magritte-color-component-input-background-state-field-hovered-v21-1-0:#1B1B1B;
33
35
  --magritte-color-component-input-background-state-field-focused-v21-1-0:#1B1B1B;
@@ -39,17 +41,18 @@
39
41
  --magritte-color-component-input-text-state-placeholder-focused-v21-1-0:#535353;
40
42
  --magritte-color-component-input-text-state-placeholder-invalid-v21-1-0:#535353;
41
43
  }
42
- .magritte-pincode-input___RpWyq_1-0-2{
44
+ .magritte-pincode-input___RpWyq_1-1-0{
43
45
  --magritte-pincode-input-padding-horizontal:20px;
44
46
  --magritte-pincode-input-padding-vertical:8px;
45
47
  --magritte-pincode-input-gap:6px;
48
+ --magritte-digit-box-width:calc((100% - (var(--magritte-pincode-input-gap) * (var(--magritte-pincode-digits-count) - 1)) - (var(--magritte-pincode-input-padding-horizontal) * 2)) / var(--magritte-pincode-digits-count));
46
49
  box-sizing:border-box;
47
50
  height:88px;
48
- border:var(--magritte-semantic-border-width-default-v21-1-0) solid var(--magritte-color-component-input-stroke-field-v21-1-0);
51
+ box-shadow:inset 0 0 0 var(--magritte-semantic-border-width-default-v21-1-0) var(--magritte-color-component-input-stroke-field-v21-1-0);
49
52
  border-radius:var(--magritte-static-border-radius-450-v21-1-0);
50
53
  background-color:var(--magritte-color-component-input-background-field-v21-1-0);
51
- padding:var(--magritte-pincode-input-padding-vertical) var(--magritte-pincode-input-padding-horizontal);
52
54
  position:relative;
55
+ overflow:clip;
53
56
  cursor:text;
54
57
  font-family:var(--magritte-font-families-body-v21-1-0);
55
58
  font-size:32px;
@@ -58,7 +61,7 @@
58
61
  line-height:40px;
59
62
  color:var(--magritte-color-component-input-text-state-content-focused-v21-1-0);
60
63
  }
61
- .magritte-pincode-input___RpWyq_1-0-2.magritte-medium___wfRSc_1-0-2{
64
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-medium___wfRSc_1-1-0{
62
65
  font-size:28px;
63
66
  --magritte-pincode-input-padding-horizontal:16px;
64
67
  --magritte-pincode-input-padding-vertical:6px;
@@ -66,78 +69,102 @@
66
69
  border-radius:var(--magritte-static-border-radius-300-v21-1-0);
67
70
  height:76px;
68
71
  }
69
- .magritte-pincode-input___RpWyq_1-0-2:hover{
70
- border-color:var(--magritte-color-component-input-stroke-state-field-hovered-v21-1-0);
72
+ .magritte-pincode-input___RpWyq_1-1-0:hover{
73
+ box-shadow:inset 0 0 0 var(--magritte-semantic-border-width-default-v21-1-0) var(--magritte-color-component-input-stroke-state-field-hovered-v21-1-0);
71
74
  background-color:var(--magritte-color-component-input-background-state-field-hovered-v21-1-0);
72
75
  }
73
- .magritte-pincode-input___RpWyq_1-0-2:hover .magritte-placeholder___10kQ0_1-0-2{
76
+ .magritte-pincode-input___RpWyq_1-1-0:hover .magritte-placeholder___10kQ0_1-1-0{
74
77
  color:var(--magritte-color-component-input-text-state-placeholder-hovered-v21-1-0);
75
78
  }
76
- .magritte-pincode-input___RpWyq_1-0-2.magritte-focus___IuhXb_1-0-2{
77
- border:var(--magritte-semantic-border-width-focused-v21-1-0) solid var(--magritte-color-component-input-stroke-state-field-focused-v21-1-0);
79
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-focus___IuhXb_1-1-0{
80
+ box-shadow:inset 0 0 0 var(--magritte-semantic-border-width-focused-v21-1-0) var(--magritte-color-component-input-stroke-state-field-focused-v21-1-0);
78
81
  background-color:var(--magritte-color-component-input-background-state-field-focused-v21-1-0);
79
- padding:var(--magritte-pincode-input-padding-vertical) calc(var(--magritte-pincode-input-padding-horizontal) - 1px);
80
82
  }
81
- .magritte-pincode-input___RpWyq_1-0-2.magritte-focus___IuhXb_1-0-2 .magritte-placeholder___10kQ0_1-0-2{
83
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-focus___IuhXb_1-1-0 .magritte-placeholder___10kQ0_1-1-0{
82
84
  color:var(--magritte-color-component-input-text-state-placeholder-focused-v21-1-0);
83
85
  }
84
- .magritte-pincode-input___RpWyq_1-0-2.magritte-invalid___hB8h2_1-0-2{
85
- border:var(--magritte-semantic-border-width-default-v21-1-0) solid var(--magritte-color-component-input-stroke-state-field-invalid-v21-1-0);
86
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-invalid___hB8h2_1-1-0{
87
+ box-shadow:inset 0 0 0 var(--magritte-semantic-border-width-default-v21-1-0) var(--magritte-color-component-input-stroke-state-field-invalid-v21-1-0);
86
88
  background-color:var(--magritte-color-component-input-background-state-field-invalid-v21-1-0);
87
89
  }
88
- .magritte-pincode-input___RpWyq_1-0-2.magritte-invalid___hB8h2_1-0-2 .magritte-placeholder___10kQ0_1-0-2{
90
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-invalid___hB8h2_1-1-0 .magritte-placeholder___10kQ0_1-1-0{
89
91
  color:var(--magritte-color-component-input-text-state-placeholder-invalid-v21-1-0);
90
92
  }
91
- .magritte-pincode-input___RpWyq_1-0-2.magritte-focus-visible___BN1eD_1-0-2{
93
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-focus-visible___BN1eD_1-1-0{
92
94
  outline:var(--magritte-color-component-input-stroke-state-accent-focused-accessible-v21-1-0) solid 4px;
93
95
  }
94
- .magritte-digit-box-container___BqMWw_1-0-2{
96
+ .magritte-pincode-input___RpWyq_1-1-0.magritte-focus-visible___BN1eD_1-1-0.magritte-invalid___hB8h2_1-1-0{
97
+ outline-color:var(--magritte-color-component-input-stroke-state-negative-focused-accessible-v21-1-0);
98
+ }
99
+ .magritte-digit-box-container___BqMWw_1-1-0{
95
100
  display:flex;
96
101
  gap:var(--magritte-pincode-input-gap);
97
- position:relative;
98
- justify-content:stretch;
102
+ position:absolute;
103
+ inset:0;
104
+ justify-content:center;
99
105
  align-items:stretch;
100
106
  height:100%;
107
+ pointer-events:none;
108
+ z-index:0;
101
109
  }
102
- .magritte-digit-box___6J0Q2_1-0-2{
103
- flex:0 0 calc((100% - (var(--magritte-pincode-input-gap) * (var(--magritte-pincode-digits-count) - 1))) / var(--magritte-pincode-digits-count));
110
+ .magritte-digit-box___6J0Q2_1-1-0{
111
+ flex:0 0 var(--magritte-digit-box-width);
112
+ pointer-events:auto;
104
113
  display:flex;
105
114
  align-items:center;
106
115
  justify-content:center;
116
+ user-select:none;
117
+ }
118
+ .magritte-digit-box___6J0Q2_1-1-0::selection{
119
+ background-color:transparent;
107
120
  }
108
- .magritte-digits-input___4QeLj_1-0-2{
121
+ .magritte-digit-box___6J0Q2_1-1-0::-moz-selection{
122
+ background-color:transparent;
123
+ }
124
+ .magritte-digits-input___4QeLj_1-1-0{
109
125
  display:block;
110
126
  position:absolute;
111
- inset:0;
127
+ top:0;
128
+ bottom:0;
129
+ --last-digit-width:max(var(--magritte-pincode-last-digit-width), 10px);
130
+ padding:0 calc(var(--last-digit-width) / 2);
131
+ left:calc(var(--magritte-pincode-input-padding-horizontal) + (var(--magritte-pincode-caret-position) * var(--magritte-digit-box-width)) + (var(--magritte-pincode-caret-position) * var(--magritte-pincode-input-gap)) + (var(--magritte-digit-box-width) / 2) - var(--last-digit-width) / 2);
132
+ height:100%;
133
+ width:var(--last-digit-width);
134
+ z-index:1;
112
135
  border-width:0;
113
136
  background:none;
114
- opacity:0;
137
+ color:transparent;
115
138
  outline:none;
116
139
  appearance:none;
117
140
  caret-color:transparent;
118
- z-index:0;
119
- pointer-events:none;
120
141
  }
121
- .magritte-digits-input___4QeLj_1-0-2::selection{
142
+ .magritte-digits-input___4QeLj_1-1-0::selection{
122
143
  background-color:transparent;
144
+ appearance:none;
123
145
  }
124
- .magritte-digits-input___4QeLj_1-0-2::-moz-selection{
146
+ .magritte-digits-input___4QeLj_1-1-0::-moz-selection{
125
147
  background-color:transparent;
148
+ appearance:none;
126
149
  }
127
- .magritte-caret-left___iZMuA_1-0-2::before,
128
- .magritte-caret-right___u8wNk_1-0-2::after{
150
+ .magritte-caret-left___iZMuA_1-1-0::before,
151
+ .magritte-caret-right___u8wNk_1-1-0::after{
129
152
  content:'';
130
153
  block-size:1em;
131
154
  border-left:1px solid var(--magritte-color-text-primary-v21-1-0);
132
- animation:magritte-caret-blink___-bhg7_1-0-2 1s steps(1) infinite;
155
+ animation:magritte-caret-blink___-bhg7_1-1-0 1s steps(1) infinite;
133
156
  }
134
- .magritte-placeholder___10kQ0_1-0-2{
157
+ .magritte-placeholder___10kQ0_1-1-0{
135
158
  color:var(--magritte-color-component-input-text-placeholder-v21-1-0);
136
159
  }
137
- .magritte-value-container___9JR3e_1-0-2{
160
+ .magritte-value-container___9JR3e_1-1-0{
138
161
  pointer-events:none;
139
162
  }
140
- @keyframes magritte-caret-blink___-bhg7_1-0-2{
163
+ .magritte-ghost-value___jPQwY_1-1-0{
164
+ display:inline-block;
165
+ visibility:hidden;
166
+ }
167
+ @keyframes magritte-caret-blink___-bhg7_1-1-0{
141
168
  50%{
142
169
  opacity:0;
143
170
  }
package/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from '@hh.ru/magritte-ui-pincode-input/PincodeInput';
2
+ export * from '@hh.ru/magritte-ui-theme-provider';
package/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import './index.css';
2
2
  export { PincodeInput } from './PincodeInput.js';
3
+ export * from '@hh.ru/magritte-ui-theme-provider';
3
4
  import 'react/jsx-runtime';
4
5
  import 'react';
5
6
  import 'classnames';
6
7
  import '@hh.ru/magritte-common-keyboard';
7
- import '@hh.ru/magritte-common-text-selection';
8
+ import '@hh.ru/magritte-common-use-multiple-refs';
8
9
  import '@hh.ru/magritte-ui-form-helper';
10
+ import './useOTP.js';
9
11
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hh.ru/magritte-ui-pincode-input",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "sideEffects": [
@@ -23,13 +23,14 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@hh.ru/magritte-common-keyboard": "4.0.4",
27
- "@hh.ru/magritte-common-text-selection": "0.1.5",
28
- "@hh.ru/magritte-ui-form-helper": "1.0.22"
26
+ "@hh.ru/magritte-common-keyboard": "4.1.0",
27
+ "@hh.ru/magritte-common-use-multiple-refs": "1.1.5",
28
+ "@hh.ru/magritte-ui-form-helper": "1.0.22",
29
+ "@hh.ru/magritte-ui-theme-provider": "1.1.37"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "classnames": ">=2.3.2",
32
33
  "react": ">=18.2.0"
33
34
  },
34
- "gitHead": "6ae3dbd40e749f4dfd123c76295ec05afc2c79e0"
35
+ "gitHead": "68b593ee06af120092b98cf659bd9800e496de23"
35
36
  }
package/useOTP.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const useOTP: (callback: (OTPValue: string | null) => void, requestCredentials?: boolean) => void;
package/useOTP.js ADDED
@@ -0,0 +1,29 @@
1
+ import './index.css';
2
+ import { useRef, useEffect } from 'react';
3
+
4
+ const useOTP = (callback, requestCredentials = true) => {
5
+ const callbackRef = useRef(callback);
6
+ callbackRef.current = callback;
7
+ useEffect(() => {
8
+ if (!('OTPCredential' in window) || !requestCredentials) {
9
+ return undefined;
10
+ }
11
+ const ac = new AbortController();
12
+ navigator.credentials
13
+ .get({
14
+ otp: { transport: ['sms'] },
15
+ signal: ac.signal,
16
+ })
17
+ // ts не знает о типе OTPCredentials
18
+ .then((otp) => callbackRef.current(otp?.code))
19
+ .catch(() => {
20
+ /* WebOTP fail, do nothing */
21
+ });
22
+ return () => {
23
+ ac.abort();
24
+ };
25
+ }, [requestCredentials]);
26
+ };
27
+
28
+ export { useOTP };
29
+ //# sourceMappingURL=useOTP.js.map
package/useOTP.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOTP.js","sources":["../src/useOTP.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nexport const useOTP = (callback: (OTPValue: string | null) => void, requestCredentials: boolean = true): void => {\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n if (!('OTPCredential' in window) || !requestCredentials) {\n return undefined;\n }\n\n const ac = new AbortController();\n\n navigator.credentials\n .get({\n otp: { transport: ['sms'] },\n signal: ac.signal,\n } as CredentialRequestOptions)\n // ts не знает о типе OTPCredentials\n .then((otp) => callbackRef.current((otp as unknown as { code: string })?.code))\n .catch(() => {\n /* WebOTP fail, do nothing */\n });\n\n return () => {\n ac.abort();\n };\n }, [requestCredentials]);\n};\n"],"names":[],"mappings":";;AAEa,MAAA,MAAM,GAAG,CAAC,QAA2C,EAAE,kBAAA,GAA8B,IAAI,KAAU;AAC5G,IAAA,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AACrC,IAAA,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IAE/B,SAAS,CAAC,MAAK;QACX,IAAI,EAAE,eAAe,IAAI,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE;AACrD,YAAA,OAAO,SAAS,CAAC;SACpB;AAED,QAAA,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;AAEjC,QAAA,SAAS,CAAC,WAAW;AAChB,aAAA,GAAG,CAAC;AACD,YAAA,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,KAAK,CAAC,EAAE;YAC3B,MAAM,EAAE,EAAE,CAAC,MAAM;SACQ,CAAC;;AAE7B,aAAA,IAAI,CAAC,CAAC,GAAG,KAAK,WAAW,CAAC,OAAO,CAAE,GAAmC,EAAE,IAAI,CAAC,CAAC;aAC9E,KAAK,CAAC,MAAK;;AAEZ,SAAC,CAAC,CAAC;AAEP,QAAA,OAAO,MAAK;YACR,EAAE,CAAC,KAAK,EAAE,CAAC;AACf,SAAC,CAAC;AACN,KAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAC7B;;;;"}