@feugene/granularity 0.48.0 → 0.49.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.
Files changed (93) hide show
  1. package/README.md +3 -3
  2. package/dist/chunks/{config-2vrOFn2f.js → config-AKpLyIGD.js} +2 -2
  3. package/dist/chunks/{config-2vrOFn2f.js.map → config-AKpLyIGD.js.map} +1 -1
  4. package/dist/chunks/config-BBtBQE3s.js +108 -0
  5. package/dist/chunks/config-BBtBQE3s.js.map +1 -0
  6. package/dist/chunks/{config-BscVxZR3.js → config-DF1ezU6y.js} +1 -1
  7. package/dist/chunks/{config-BscVxZR3.js.map → config-DF1ezU6y.js.map} +1 -1
  8. package/dist/chunks/config-MUkhktTP.js +267 -0
  9. package/dist/chunks/config-MUkhktTP.js.map +1 -0
  10. package/dist/chunks/{en-Bxyn5lW7.js → en-CsQUoBS2.js} +5 -1
  11. package/dist/chunks/en-CsQUoBS2.js.map +1 -0
  12. package/dist/chunks/{es-Dtl-a7ui.js → es-9nbkPvdA.js} +5 -1
  13. package/dist/chunks/es-9nbkPvdA.js.map +1 -0
  14. package/dist/chunks/{es-Bj_vc18Z.js → es-DrUz5J6I.js} +3 -3
  15. package/dist/chunks/{es-Bj_vc18Z.js.map → es-DrUz5J6I.js.map} +1 -1
  16. package/dist/chunks/{generated-hELWB0gZ.js → generated-CxvC5O0V.js} +42 -0
  17. package/dist/chunks/{generated-hELWB0gZ.js.map → generated-CxvC5O0V.js.map} +1 -1
  18. package/dist/chunks/{grBadgeStyles-DV0DrAew.js → grBadgeStyles-D51AXbj5.js} +57 -1
  19. package/dist/chunks/grBadgeStyles-D51AXbj5.js.map +1 -0
  20. package/dist/chunks/{grChipStyles-DcDyxAL0.js → grChipStyles-CA9Wj1_E.js} +1 -1
  21. package/dist/chunks/{grChipStyles-DcDyxAL0.js.map → grChipStyles-CA9Wj1_E.js.map} +1 -1
  22. package/dist/chunks/{granular-provider-BOQp7N5o.js → granular-provider-B6xomapW.js} +5 -3
  23. package/dist/chunks/granular-provider-B6xomapW.js.map +1 -0
  24. package/dist/chunks/{ru-hrB4I1u_.js → ru-88i1WG0b.js} +5 -1
  25. package/dist/chunks/ru-88i1WG0b.js.map +1 -0
  26. package/dist/chunks/{safelist-x8USecK8.js → safelist-CSfynUC1.js} +5 -1
  27. package/dist/chunks/safelist-CSfynUC1.js.map +1 -0
  28. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-C-uovdv8.js → GrAutocomplete-Chhw65pI.js} +2 -2
  29. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-C-uovdv8.js.map → GrAutocomplete-Chhw65pI.js.map} +1 -1
  30. package/dist/components/GrAutocomplete/index.js +1 -1
  31. package/dist/components/GrBadge/chunks/GrBadge-C42KLyUD.js +71 -0
  32. package/dist/components/GrBadge/chunks/GrBadge-C42KLyUD.js.map +1 -0
  33. package/dist/components/GrBadge/index.js +3 -3
  34. package/dist/components/GrBadge/styles.css +1 -1
  35. package/dist/components/GrChip/chunks/{GrChip-CKqyzYcv.js → GrChip-DoyS1Dr4.js} +1 -1
  36. package/dist/components/GrChip/chunks/{GrChip-CKqyzYcv.js.map → GrChip-DoyS1Dr4.js.map} +1 -1
  37. package/dist/components/GrChip/index.js +2 -2
  38. package/dist/components/GrInputTag/chunks/{GrInputTag-CO0YU-ok.js → GrInputTag-qnRdAZnn.js} +2 -2
  39. package/dist/components/GrInputTag/chunks/{GrInputTag-CO0YU-ok.js.map → GrInputTag-qnRdAZnn.js.map} +1 -1
  40. package/dist/components/GrInputTag/index.js +1 -1
  41. package/dist/components/GrOtpInput/chunks/GrOtpInput-B_Q-RDLw.js +419 -0
  42. package/dist/components/GrOtpInput/chunks/GrOtpInput-B_Q-RDLw.js.map +1 -0
  43. package/dist/components/GrOtpInput/index.js +3 -0
  44. package/dist/components/GrOtpInput/styles.css +1 -0
  45. package/dist/components/GrPagination/chunks/{GrPagination-BbaF5eXk.js → GrPagination-CMrLPzvL.js} +1 -1
  46. package/dist/components/GrPagination/chunks/{GrPagination-BbaF5eXk.js.map → GrPagination-CMrLPzvL.js.map} +1 -1
  47. package/dist/components/GrPagination/index.js +1 -1
  48. package/dist/components/GrSelect/chunks/{GrSelect-0iI4wdR_.js → GrSelect-DvSq-nV-.js} +2 -2
  49. package/dist/components/GrSelect/chunks/{GrSelect-0iI4wdR_.js.map → GrSelect-DvSq-nV-.js.map} +1 -1
  50. package/dist/components/GrSelect/index.js +1 -1
  51. package/dist/components/GrSwitch/chunks/{GrSwitch-CriHWT2A.js → GrSwitch-D7X42uIA.js} +61 -7
  52. package/dist/components/GrSwitch/chunks/GrSwitch-D7X42uIA.js.map +1 -0
  53. package/dist/components/GrSwitch/index.js +2 -2
  54. package/dist/granular-provider-node.js +1 -1
  55. package/dist/granular-provider.js +1 -1
  56. package/dist/i18n/all.js +1 -1
  57. package/dist/i18n/index.js +1 -1
  58. package/dist/index.js +14 -13
  59. package/dist/theme.js +1 -1
  60. package/dist/tokens.js +1 -1
  61. package/dist/types/components/GrBadge/GrBadge.vue.d.ts +15 -1
  62. package/dist/types/components/GrBadge/grBadgeStyles.d.ts +57 -0
  63. package/dist/types/components/GrBadge/index.d.ts +1 -1
  64. package/dist/types/components/GrOtpInput/GrOtpInput.vue.d.ts +164 -0
  65. package/dist/types/components/GrOtpInput/config.d.ts +1 -0
  66. package/dist/types/components/GrOtpInput/defaults.d.ts +15 -0
  67. package/dist/types/components/GrOtpInput/grOtpInputStyles.d.ts +62 -0
  68. package/dist/types/components/GrOtpInput/index.d.ts +9 -0
  69. package/dist/types/components/GrOtpInput/otpValue.d.ts +71 -0
  70. package/dist/types/components/GrOtpInput/safelist.d.ts +7 -0
  71. package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +42 -4
  72. package/dist/types/components/GrSwitch/defaults.d.ts +2 -0
  73. package/dist/types/components/GrSwitch/grSwitchStyles.d.ts +129 -25
  74. package/dist/types/components/GrSwitch/index.d.ts +1 -1
  75. package/dist/types/components/GrSwitch/safelist.d.ts +4 -3
  76. package/dist/types/granular-provider/shared.d.ts +1 -0
  77. package/dist/types/i18n/messages/en.d.ts +4 -0
  78. package/dist/types/i18n/messages/es.d.ts +4 -0
  79. package/dist/types/i18n/messages/ru.d.ts +4 -0
  80. package/dist/types/index.d.ts +1 -0
  81. package/dist/web-types.json +254 -8
  82. package/package.json +5 -1
  83. package/dist/chunks/config-Yy67uWHY.js +0 -140
  84. package/dist/chunks/config-Yy67uWHY.js.map +0 -1
  85. package/dist/chunks/en-Bxyn5lW7.js.map +0 -1
  86. package/dist/chunks/es-Dtl-a7ui.js.map +0 -1
  87. package/dist/chunks/grBadgeStyles-DV0DrAew.js.map +0 -1
  88. package/dist/chunks/granular-provider-BOQp7N5o.js.map +0 -1
  89. package/dist/chunks/ru-hrB4I1u_.js.map +0 -1
  90. package/dist/chunks/safelist-x8USecK8.js.map +0 -1
  91. package/dist/components/GrBadge/chunks/GrBadge-DBeQXEir.js +0 -47
  92. package/dist/components/GrBadge/chunks/GrBadge-DBeQXEir.js.map +0 -1
  93. package/dist/components/GrSwitch/chunks/GrSwitch-CriHWT2A.js.map +0 -1
@@ -0,0 +1,419 @@
1
+ import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunks/context-CPMRfwVr.js";
2
+ import { useGrFormControl } from "../../../composables/useGrFormControl.js";
3
+ import { a as grOtpInputCellClass, c as separatorClass, i as fieldClass, o as grOtpInputRootClass, r as caretClass, s as placeholderClass } from "../../../chunks/config-BBtBQE3s.js";
4
+ import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watch, watchEffect } from "vue";
5
+ import '../styles.css';//#region src/components/GrOtpInput/otpValue.ts
6
+ /**
7
+ * Алфавиты по типу. `text` — любой непробельный символ: пробел в коде не несёт
8
+ * смысла, а при вставке из мессенджера приходит вместе с ним.
9
+ */
10
+ var OTP_TYPE_PATTERNS = {
11
+ numeric: "[0-9]",
12
+ alphanumeric: "[0-9A-Za-z]",
13
+ text: "\\S"
14
+ };
15
+ /** Раскладка клавиатуры на мобильном: цифровой блок только там, где он уместен. */
16
+ var OTP_TYPE_INPUT_MODES = {
17
+ numeric: "numeric",
18
+ alphanumeric: "text",
19
+ text: "text"
20
+ };
21
+ /**
22
+ * Матчер алфавита. Своя регулярка потребителя перекрывает тип; сломанная —
23
+ * повод сказать об этом в dev и вернуться к типу, а не уронить рендер.
24
+ */
25
+ function createOtpMatcher(type, pattern) {
26
+ const source = pattern ?? OTP_TYPE_PATTERNS[type];
27
+ let regexp;
28
+ try {
29
+ regexp = new RegExp(`^(?:${source})$`, "u");
30
+ } catch {
31
+ if (process.env.NODE_ENV !== "production") console.warn(`[granularity] GrOtpInput: \`pattern\` «${source}» не компилируется — алфавит взят из \`type="${type}"\`.`);
32
+ regexp = new RegExp(`^(?:${OTP_TYPE_PATTERNS[type]})$`, "u");
33
+ }
34
+ return (char) => regexp.test(char);
35
+ }
36
+ /**
37
+ * Свести сырое содержимое поля к коду фиксированной длины.
38
+ *
39
+ * Одна функция на все источники ввода — символ с клавиатуры, вставка из буфера,
40
+ * подстановка кода из SMS, — потому что различить их по событию нельзя:
41
+ * мобильные клавиатуры и автозаполнение присылают целую строку одним `input`.
42
+ *
43
+ * Три шага, и каждый закрывает свой отказ:
44
+ *
45
+ * 1. **Отсев чужих символов с переносом каретки.** Индексы после отсева
46
+ * съезжают, и каретку надо считать в новых координатах, иначе вставка
47
+ * `123-456` поставит её на два символа правее конца.
48
+ * 2. **Печать заменяет, а не сдвигает.** Ячейка держит ровно один символ, и
49
+ * напечатанное в неё занимает её место, а хвост кода остаётся где стоял.
50
+ * Правило безусловное, а не «когда поле заполнено»: иначе одно и то же
51
+ * нажатие в начале кода то заменяло бы символ, то двигало весь остаток —
52
+ * в зависимости от того, сколько ячеек занято.
53
+ * 3. **Обрезка по длине.** Вставка длиннее поля режется со стороны каретки:
54
+ * выкусывать за ней уже нечего, а сдвигать набранное нельзя.
55
+ */
56
+ function normalizeOtpInput(options) {
57
+ const { previous, raw, caret, length, matcher, uppercase } = options;
58
+ let kept = "";
59
+ let keptCaret = -1;
60
+ for (let index = 0; index < raw.length; index += 1) {
61
+ if (index === caret) keptCaret = kept.length;
62
+ const char = raw[index];
63
+ if (matcher(char)) kept += uppercase ? char.toUpperCase() : char;
64
+ }
65
+ if (keptCaret === -1) keptCaret = kept.length;
66
+ const inserted = kept.length - previous.length;
67
+ let value = inserted > 0 ? kept.slice(0, keptCaret) + kept.slice(keptCaret + inserted) : kept;
68
+ if (value.length <= length) return {
69
+ value,
70
+ caret: Math.min(keptCaret, value.length)
71
+ };
72
+ const excess = value.length - length;
73
+ const cut = Math.max(0, keptCaret - excess);
74
+ value = value.slice(0, cut) + value.slice(keptCaret);
75
+ return {
76
+ value: value.slice(0, length),
77
+ caret: Math.min(cut, length)
78
+ };
79
+ }
80
+ /** Отсев без каретки — для значения, пришедшего из `v-model`. */
81
+ function sanitizeOtpValue(value, options) {
82
+ return normalizeOtpInput({
83
+ previous: "",
84
+ raw: value,
85
+ caret: value.length,
86
+ length: options.length,
87
+ matcher: options.matcher,
88
+ uppercase: options.uppercase
89
+ }).value;
90
+ }
91
+ /**
92
+ * Индексы ячеек, **перед** которыми стоит разделитель.
93
+ *
94
+ * Разбивка, не сходящаяся с длиной, — ошибка потребителя, а не повод рисовать
95
+ * половину: группы игнорируются целиком, о чём говорит предупреждение в вызове.
96
+ */
97
+ function otpSeparatorIndexes(length, groups) {
98
+ const indexes = /* @__PURE__ */ new Set();
99
+ if (!groups?.length) return indexes;
100
+ if (groups.reduce((sum, size) => sum + size, 0) !== length || groups.some((size) => size <= 0)) return indexes;
101
+ let offset = 0;
102
+ for (const size of groups.slice(0, -1)) {
103
+ offset += size;
104
+ indexes.add(offset);
105
+ }
106
+ return indexes;
107
+ }
108
+ function isOtpGroupsValid(length, groups) {
109
+ if (!groups?.length) return true;
110
+ return groups.every((size) => size > 0) && groups.reduce((sum, size) => sum + size, 0) === length;
111
+ }
112
+ //#endregion
113
+ //#region src/components/GrOtpInput/GrOtpInput.vue?vue&type=script&setup=true&lang.ts
114
+ var _hoisted_1 = [
115
+ "id",
116
+ "value",
117
+ "name",
118
+ "form",
119
+ "disabled",
120
+ "readonly",
121
+ "required",
122
+ "aria-label",
123
+ "aria-describedby",
124
+ "aria-invalid",
125
+ "aria-required",
126
+ "inputmode",
127
+ "autocomplete"
128
+ ];
129
+ //#endregion
130
+ //#region src/components/GrOtpInput/GrOtpInput.vue
131
+ var GrOtpInput_default = /* @__PURE__ */ defineComponent({
132
+ __name: "GrOtpInput",
133
+ props: {
134
+ modelValue: { default: "" },
135
+ length: { default: void 0 },
136
+ type: { default: "numeric" },
137
+ pattern: { default: void 0 },
138
+ groups: { default: void 0 },
139
+ masked: {
140
+ type: Boolean,
141
+ default: void 0
142
+ },
143
+ placeholder: { default: "" },
144
+ size: { default: void 0 },
145
+ autofocus: {
146
+ type: Boolean,
147
+ default: false
148
+ },
149
+ oneTimeCode: {
150
+ type: Boolean,
151
+ default: true
152
+ },
153
+ webOtp: {
154
+ type: Boolean,
155
+ default: false
156
+ },
157
+ name: { default: void 0 },
158
+ form: { default: void 0 },
159
+ disabled: {
160
+ type: Boolean,
161
+ default: false
162
+ },
163
+ readonly: {
164
+ type: Boolean,
165
+ default: false
166
+ },
167
+ invalid: {
168
+ type: Boolean,
169
+ default: false
170
+ },
171
+ required: {
172
+ type: Boolean,
173
+ default: false
174
+ },
175
+ ariaLabel: { default: void 0 }
176
+ },
177
+ emits: [
178
+ "update:modelValue",
179
+ "change",
180
+ "focus",
181
+ "blur",
182
+ "complete"
183
+ ],
184
+ setup(__props, { expose: __expose, emit: __emit }) {
185
+ const props = __props;
186
+ const emit = __emit;
187
+ const { disabled: isDisabled, invalid: isInvalid, required: isRequired, readonly: isReadonly, id: fieldId, describedBy } = useGrFormControl(() => props);
188
+ const resolvedSize = useGrComponentSize(() => props.size, { component: "GrOtpInput" });
189
+ const resolvedLength = useGrComponentProp("GrOtpInput", "length", () => props.length, 6);
190
+ const resolvedMasked = useGrComponentProp("GrOtpInput", "masked", () => props.masked, false);
191
+ const fieldEl = ref(null);
192
+ const caret = ref(0);
193
+ const focused = ref(false);
194
+ const matcher = computed(() => createOtpMatcher(props.type, props.pattern));
195
+ /** Коды печатают капсом, и раскладка «то ли a, то ли A» тут только мешает. */
196
+ const uppercase = computed(() => props.type === "alphanumeric" && props.pattern === void 0);
197
+ const value = computed(() => sanitizeOtpValue(props.modelValue, {
198
+ length: resolvedLength.value,
199
+ matcher: matcher.value,
200
+ uppercase: uppercase.value
201
+ }));
202
+ const characters = computed(() => {
203
+ const chars = value.value.split("");
204
+ return Array.from({ length: resolvedLength.value }, (_, index) => chars[index] ?? "");
205
+ });
206
+ const separators = computed(() => otpSeparatorIndexes(resolvedLength.value, props.groups));
207
+ const activeIndex = computed(() => {
208
+ if (!focused.value || isDisabled.value || isReadonly.value) return -1;
209
+ return Math.min(caret.value, resolvedLength.value - 1);
210
+ });
211
+ const rootClass = computed(() => grOtpInputRootClass(resolvedSize.value));
212
+ function cellClass(index) {
213
+ return grOtpInputCellClass({
214
+ size: resolvedSize.value,
215
+ active: index === activeIndex.value,
216
+ invalid: isInvalid.value,
217
+ disabled: isDisabled.value
218
+ });
219
+ }
220
+ /**
221
+ * Значение живёт в пропе, а поле — в DOM, и после каждой правки их надо свести:
222
+ * браузер уже применил свою вставку, а нормализация могла дать другую строку.
223
+ */
224
+ function writeField(next, nextCaret) {
225
+ const el = fieldEl.value;
226
+ if (!el) return;
227
+ if (el.value !== next) el.value = next;
228
+ const position = Math.min(nextCaret, next.length);
229
+ el.setSelectionRange(position, position);
230
+ caret.value = position;
231
+ }
232
+ function commit(next, nextCaret) {
233
+ writeField(next, nextCaret);
234
+ if (next !== value.value) {
235
+ emit("update:modelValue", next);
236
+ emit("change", next);
237
+ }
238
+ }
239
+ function onInput(event) {
240
+ const el = event.target;
241
+ const normalized = normalizeOtpInput({
242
+ previous: value.value,
243
+ raw: el.value,
244
+ caret: el.selectionStart ?? el.value.length,
245
+ length: resolvedLength.value,
246
+ matcher: matcher.value,
247
+ uppercase: uppercase.value
248
+ });
249
+ commit(normalized.value, normalized.caret);
250
+ }
251
+ function syncCaret() {
252
+ const el = fieldEl.value;
253
+ if (el) caret.value = el.selectionStart ?? 0;
254
+ }
255
+ /**
256
+ * Клик ставит каретку в первую пустую ячейку, а не туда, куда пришёлся курсор.
257
+ *
258
+ * Ячейки — декорация: попадание мышью по невидимому тексту ничего осмысленного
259
+ * не значит, а «продолжить с того места, где остановился» — единственное, чего
260
+ * от кода ждут. Редактирование середины остаётся за стрелками.
261
+ */
262
+ function onPointerUp() {
263
+ const el = fieldEl.value;
264
+ if (!el || isDisabled.value || isReadonly.value) return;
265
+ const position = value.value.length;
266
+ el.setSelectionRange(position, position);
267
+ caret.value = position;
268
+ }
269
+ function onFocus(event) {
270
+ focused.value = true;
271
+ syncCaret();
272
+ emit("focus", event);
273
+ }
274
+ function onBlur(event) {
275
+ focused.value = false;
276
+ emit("blur", event);
277
+ }
278
+ function focus() {
279
+ fieldEl.value?.focus();
280
+ }
281
+ function blur() {
282
+ fieldEl.value?.blur();
283
+ }
284
+ /** Штатный сценарий: сервер отверг код, поле чистится и снова ждёт ввода. */
285
+ function clear() {
286
+ commit("", 0);
287
+ focus();
288
+ }
289
+ __expose({
290
+ focus,
291
+ blur,
292
+ clear
293
+ });
294
+ watch(value, (next) => {
295
+ const el = fieldEl.value;
296
+ if (el && el.value !== next) writeField(next, next.length);
297
+ }, { flush: "post" });
298
+ /**
299
+ * Повторный `complete` на том же значении не шлётся: правка последней ячейки
300
+ * туда-обратно не должна вызывать второй запрос на проверку кода.
301
+ */
302
+ const completed = ref(null);
303
+ watch(value, (next) => {
304
+ if (next.length < resolvedLength.value) {
305
+ completed.value = null;
306
+ return;
307
+ }
308
+ if (completed.value === next) return;
309
+ completed.value = next;
310
+ emit("complete", next);
311
+ }, { immediate: true });
312
+ let otpRequest;
313
+ function abortWebOtp() {
314
+ otpRequest?.abort();
315
+ otpRequest = void 0;
316
+ }
317
+ /**
318
+ * WebOTP — второй канал автоподстановки, закрывающий Android так же, как
319
+ * `one-time-code` закрывает iOS.
320
+ *
321
+ * Живёт в `onMounted`, а не в теле setup: `navigator` на сервере роняет рендер
322
+ * (`docs/ssr.md`). Отказ пользователя, отсутствие API и отмена — не ошибки:
323
+ * поле просто остаётся пустым и ждёт ручного ввода.
324
+ */
325
+ function requestWebOtp() {
326
+ abortWebOtp();
327
+ if (typeof window === "undefined" || !("OTPCredential" in window)) return;
328
+ const controller = new AbortController();
329
+ otpRequest = controller;
330
+ navigator.credentials.get({
331
+ otp: { transport: ["sms"] },
332
+ signal: controller.signal
333
+ }).then((credential) => {
334
+ if (otpRequest !== controller || !credential?.code) return;
335
+ const normalized = sanitizeOtpValue(credential.code, {
336
+ length: resolvedLength.value,
337
+ matcher: matcher.value,
338
+ uppercase: uppercase.value
339
+ });
340
+ commit(normalized, normalized.length);
341
+ }).catch(() => {});
342
+ }
343
+ onMounted(() => {
344
+ if (props.autofocus) focus();
345
+ if (props.webOtp) requestWebOtp();
346
+ });
347
+ watch(value, (next) => {
348
+ if (next.length >= resolvedLength.value) abortWebOtp();
349
+ });
350
+ onBeforeUnmount(abortWebOtp);
351
+ if (process.env.NODE_ENV !== "production") watchEffect(() => {
352
+ if (!isOtpGroupsValid(resolvedLength.value, props.groups)) console.warn(`[granularity] GrOtpInput: сумма \`groups\` не равна \`length\` (${resolvedLength.value}) — разбивка игнорируется целиком. Половина разбивки хуже её отсутствия: разделитель встал бы не там, где группа кончается.`);
353
+ if (resolvedLength.value < 1) console.warn("[granularity] GrOtpInput: `length` меньше единицы — рисовать нечего.");
354
+ });
355
+ return (_ctx, _cache) => {
356
+ return openBlock(), createElementBlock("div", {
357
+ "data-testid": "gr-otp-input",
358
+ "data-gr-otp-input": "",
359
+ class: normalizeClass(rootClass.value)
360
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(characters.value, (char, index) => {
361
+ return openBlock(), createElementBlock(Fragment, { key: index }, [separators.value.has(index) ? (openBlock(), createElementBlock("span", {
362
+ key: 0,
363
+ "data-gr-otp-input-separator": "",
364
+ class: normalizeClass(unref(separatorClass)),
365
+ "aria-hidden": "true"
366
+ }, [renderSlot(_ctx.$slots, "separator", { index }, () => [_cache[0] || (_cache[0] = createTextVNode("—", -1))])], 2)) : createCommentVNode("", true), createElementVNode("span", {
367
+ "data-testid": "gr-otp-input-cell",
368
+ "data-gr-otp-input-cell": "",
369
+ class: normalizeClass(cellClass(index)),
370
+ "aria-hidden": "true"
371
+ }, [renderSlot(_ctx.$slots, "cell", {
372
+ char,
373
+ index,
374
+ active: index === activeIndex.value,
375
+ filled: char !== "",
376
+ masked: unref(resolvedMasked)
377
+ }, () => [char ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(unref(resolvedMasked) ? "•" : char), 1)], 64)) : __props.placeholder ? (openBlock(), createElementBlock("span", {
378
+ key: 1,
379
+ class: normalizeClass(unref(placeholderClass))
380
+ }, toDisplayString(__props.placeholder), 3)) : createCommentVNode("", true)]), index === activeIndex.value && !char ? (openBlock(), createElementBlock("span", {
381
+ key: 0,
382
+ "data-gr-otp-input-caret": "",
383
+ class: normalizeClass(unref(caretClass))
384
+ }, null, 2)) : createCommentVNode("", true)], 2)], 64);
385
+ }), 128)), createElementVNode("input", {
386
+ id: unref(fieldId),
387
+ ref_key: "fieldEl",
388
+ ref: fieldEl,
389
+ "data-testid": "gr-otp-input-field",
390
+ "data-gr-otp-input-field": "",
391
+ type: "text",
392
+ class: normalizeClass(unref(fieldClass)),
393
+ value: value.value,
394
+ name: __props.name,
395
+ form: __props.form,
396
+ disabled: unref(isDisabled),
397
+ readonly: unref(isReadonly),
398
+ required: unref(isRequired),
399
+ "aria-label": __props.ariaLabel,
400
+ "aria-describedby": unref(describedBy),
401
+ "aria-invalid": unref(isInvalid) ? "true" : void 0,
402
+ "aria-required": unref(isRequired) ? "true" : void 0,
403
+ inputmode: unref(OTP_TYPE_INPUT_MODES)[__props.type],
404
+ autocomplete: __props.oneTimeCode ? "one-time-code" : "off",
405
+ autocapitalize: "none",
406
+ autocorrect: "off",
407
+ spellcheck: "false",
408
+ onInput,
409
+ onSelect: syncCaret,
410
+ onKeyup: syncCaret,
411
+ onPointerup: onPointerUp,
412
+ onFocus,
413
+ onBlur
414
+ }, null, 42, _hoisted_1)], 2);
415
+ };
416
+ }
417
+ });
418
+ //#endregion
419
+ export { GrOtpInput_default as t };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrOtpInput-B_Q-RDLw.js","names":[],"sources":["../../../../src/components/GrOtpInput/otpValue.ts","../../../../src/components/GrOtpInput/GrOtpInput.vue","../../../../src/components/GrOtpInput/GrOtpInput.vue"],"sourcesContent":["/**\n * Чистая логика значения OTP-поля: алфавит, нормализация ввода и разбивка на\n * группы. Без Vue и без DOM — здесь дёшево перебрать края, а края тут и есть\n * весь риск.\n */\n\nexport const GR_OTP_INPUT_TYPES = ['numeric', 'alphanumeric', 'text'] as const\nexport type GrOtpInputType = typeof GR_OTP_INPUT_TYPES[number]\n\n/**\n * Алфавиты по типу. `text` — любой непробельный символ: пробел в коде не несёт\n * смысла, а при вставке из мессенджера приходит вместе с ним.\n */\nexport const OTP_TYPE_PATTERNS: Record<GrOtpInputType, string> = {\n numeric: '[0-9]',\n alphanumeric: '[0-9A-Za-z]',\n text: '\\\\S',\n}\n\n/** Раскладка клавиатуры на мобильном: цифровой блок только там, где он уместен. */\nexport const OTP_TYPE_INPUT_MODES: Record<GrOtpInputType, 'numeric' | 'text'> = {\n numeric: 'numeric',\n alphanumeric: 'text',\n text: 'text',\n}\n\nexport type OtpMatcher = (char: string) => boolean\n\n/**\n * Матчер алфавита. Своя регулярка потребителя перекрывает тип; сломанная —\n * повод сказать об этом в dev и вернуться к типу, а не уронить рендер.\n */\nexport function createOtpMatcher(type: GrOtpInputType, pattern?: string): OtpMatcher {\n const source = pattern ?? OTP_TYPE_PATTERNS[type]\n\n let regexp: RegExp\n try {\n regexp = new RegExp(`^(?:${source})$`, 'u')\n }\n catch {\n if (__GR_DEV__) {\n console.warn(\n `[granularity] GrOtpInput: \\`pattern\\` «${source}» не компилируется — `\n + `алфавит взят из \\`type=\"${type}\"\\`.`,\n )\n }\n regexp = new RegExp(`^(?:${OTP_TYPE_PATTERNS[type]})$`, 'u')\n }\n\n return char => regexp.test(char)\n}\n\nexport interface NormalizeOtpOptions {\n /** Значение до правки: по нему считается, сколько символов вставлено. */\n previous: string\n /** Сырое содержимое поля после события `input`. */\n raw: string\n /** Каретка в координатах **сырой** строки. */\n caret: number\n length: number\n matcher: OtpMatcher\n /** Приводить ли к верхнему регистру: коды печатают капсом. */\n uppercase: boolean\n}\n\nexport interface NormalizedOtp {\n value: string\n caret: number\n}\n\n/**\n * Свести сырое содержимое поля к коду фиксированной длины.\n *\n * Одна функция на все источники ввода — символ с клавиатуры, вставка из буфера,\n * подстановка кода из SMS, — потому что различить их по событию нельзя:\n * мобильные клавиатуры и автозаполнение присылают целую строку одним `input`.\n *\n * Три шага, и каждый закрывает свой отказ:\n *\n * 1. **Отсев чужих символов с переносом каретки.** Индексы после отсева\n * съезжают, и каретку надо считать в новых координатах, иначе вставка\n * `123-456` поставит её на два символа правее конца.\n * 2. **Печать заменяет, а не сдвигает.** Ячейка держит ровно один символ, и\n * напечатанное в неё занимает её место, а хвост кода остаётся где стоял.\n * Правило безусловное, а не «когда поле заполнено»: иначе одно и то же\n * нажатие в начале кода то заменяло бы символ, то двигало весь остаток —\n * в зависимости от того, сколько ячеек занято.\n * 3. **Обрезка по длине.** Вставка длиннее поля режется со стороны каретки:\n * выкусывать за ней уже нечего, а сдвигать набранное нельзя.\n */\nexport function normalizeOtpInput(options: NormalizeOtpOptions): NormalizedOtp {\n const { previous, raw, caret, length, matcher, uppercase } = options\n\n let kept = ''\n let keptCaret = -1\n\n for (let index = 0; index < raw.length; index += 1) {\n if (index === caret)\n keptCaret = kept.length\n\n const char = raw[index]\n if (matcher(char))\n kept += uppercase ? char.toUpperCase() : char\n }\n\n if (keptCaret === -1)\n keptCaret = kept.length\n\n const inserted = kept.length - previous.length\n let value = inserted > 0\n ? kept.slice(0, keptCaret) + kept.slice(keptCaret + inserted)\n : kept\n\n if (value.length <= length)\n return { value, caret: Math.min(keptCaret, value.length) }\n\n const excess = value.length - length\n const cut = Math.max(0, keptCaret - excess)\n value = value.slice(0, cut) + value.slice(keptCaret)\n\n return { value: value.slice(0, length), caret: Math.min(cut, length) }\n}\n\n/** Отсев без каретки — для значения, пришедшего из `v-model`. */\nexport function sanitizeOtpValue(\n value: string,\n options: { length: number, matcher: OtpMatcher, uppercase: boolean },\n): string {\n return normalizeOtpInput({\n previous: '',\n raw: value,\n caret: value.length,\n length: options.length,\n matcher: options.matcher,\n uppercase: options.uppercase,\n }).value\n}\n\n/**\n * Индексы ячеек, **перед** которыми стоит разделитель.\n *\n * Разбивка, не сходящаяся с длиной, — ошибка потребителя, а не повод рисовать\n * половину: группы игнорируются целиком, о чём говорит предупреждение в вызове.\n */\nexport function otpSeparatorIndexes(length: number, groups?: number[]): Set<number> {\n const indexes = new Set<number>()\n if (!groups?.length)\n return indexes\n\n const total = groups.reduce((sum, size) => sum + size, 0)\n if (total !== length || groups.some(size => size <= 0))\n return indexes\n\n let offset = 0\n for (const size of groups.slice(0, -1)) {\n offset += size\n indexes.add(offset)\n }\n\n return indexes\n}\n\nexport function isOtpGroupsValid(length: number, groups?: number[]): boolean {\n if (!groups?.length)\n return true\n\n return groups.every(size => size > 0) && groups.reduce((sum, size) => sum + size, 0) === length\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch, watchEffect } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../../composables/useGrComponentConfig'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\n\nimport {\n caretClass,\n fieldClass,\n grOtpInputCellClass,\n grOtpInputRootClass,\n placeholderClass,\n separatorClass,\n type GrOtpInputSize,\n} from './grOtpInputStyles'\nimport {\n createOtpMatcher,\n isOtpGroupsValid,\n normalizeOtpInput,\n OTP_TYPE_INPUT_MODES,\n otpSeparatorIndexes,\n sanitizeOtpValue,\n type GrOtpInputType,\n} from './otpValue'\n\nexport type { GrOtpInputSize } from './grOtpInputStyles'\nexport type { GrOtpInputType } from './otpValue'\n\nexport interface GrOtpInputProps {\n /** Код целиком, без разделителей. */\n modelValue?: string\n /** Число ячеек. */\n length?: number\n /** Допустимый алфавит и раскладка мобильной клавиатуры. */\n type?: GrOtpInputType\n /** Свой алфавит регуляркой на **один символ**. Перекрывает `type`. */\n pattern?: string\n /** Разбивка на группы: `[3, 3]` рисует `123 – 456`. Сумма обязана равняться `length`. */\n groups?: number[]\n /** Точки вместо символов — сценарий PIN. На `modelValue` не влияет. */\n masked?: boolean\n /** Знак в пустой ячейке. */\n placeholder?: string\n size?: GrOtpInputSize\n autofocus?: boolean\n /** `autocomplete=\"one-time-code\"`: код из SMS подставляется на iOS и macOS. */\n oneTimeCode?: boolean\n /**\n * Приём кода из SMS на Android через WebOTP. Выключен: требует HTTPS и\n * особого хвоста в тексте сообщения, который добавляет бэкенд.\n */\n webOtp?: boolean\n /** Имя поля для нативной отправки формы. */\n name?: string\n /** `id` формы, если поле лежит вне неё. */\n form?: string\n disabled?: boolean\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrOtpInputEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /** Код набран целиком. Точка автосабмита. */\n (e: 'complete', value: string): void\n}\n\nconst props = withDefaults(\n defineProps<GrOtpInputProps>(),\n {\n modelValue: '',\n // `length`, `size` и `masked` настраиваются через `GrConfigProvider`,\n // поэтому их дефолты живут в резолверах ниже, а не в `withDefaults`.\n length: undefined,\n type: 'numeric',\n pattern: undefined,\n groups: undefined,\n masked: undefined,\n placeholder: '',\n size: undefined,\n autofocus: false,\n oneTimeCode: true,\n webOtp: false,\n name: undefined,\n form: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n },\n)\n\nconst emit = defineEmits<GrOtpInputEmits>()\n\ndefineSlots<{\n /** Своё оформление ячейки. Размер и рамку по-прежнему держит компонент. */\n cell?: (props: {\n char: string\n index: number\n active: boolean\n filled: boolean\n masked: boolean\n }) => any\n /** Знак между группами. По умолчанию — тире. */\n separator?: (props: { index: number }) => any\n}>()\n\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n id: fieldId,\n describedBy,\n} = useGrFormControl(() => props)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrOtpInput' })\nconst resolvedLength = useGrComponentProp('GrOtpInput', 'length', () => props.length, 6)\nconst resolvedMasked = useGrComponentProp('GrOtpInput', 'masked', () => props.masked, false)\n\nconst fieldEl = ref<HTMLInputElement | null>(null)\nconst caret = ref(0)\nconst focused = ref(false)\n\nconst matcher = computed(() => createOtpMatcher(props.type, props.pattern))\n\n/** Коды печатают капсом, и раскладка «то ли a, то ли A» тут только мешает. */\nconst uppercase = computed(() => props.type === 'alphanumeric' && props.pattern === undefined)\n\nconst value = computed(() => sanitizeOtpValue(props.modelValue, {\n length: resolvedLength.value,\n matcher: matcher.value,\n uppercase: uppercase.value,\n}))\n\nconst characters = computed(() => {\n const chars = value.value.split('')\n return Array.from({ length: resolvedLength.value }, (_, index) => chars[index] ?? '')\n})\n\nconst separators = computed(() => otpSeparatorIndexes(resolvedLength.value, props.groups))\n\nconst activeIndex = computed(() => {\n if (!focused.value || isDisabled.value || isReadonly.value)\n return -1\n\n return Math.min(caret.value, resolvedLength.value - 1)\n})\n\nconst rootClass = computed(() => grOtpInputRootClass(resolvedSize.value))\n\nfunction cellClass(index: number): string {\n return grOtpInputCellClass({\n size: resolvedSize.value,\n active: index === activeIndex.value,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n}\n\n/**\n * Значение живёт в пропе, а поле — в DOM, и после каждой правки их надо свести:\n * браузер уже применил свою вставку, а нормализация могла дать другую строку.\n */\nfunction writeField(next: string, nextCaret: number): void {\n const el = fieldEl.value\n if (!el)\n return\n\n if (el.value !== next)\n el.value = next\n\n const position = Math.min(nextCaret, next.length)\n el.setSelectionRange(position, position)\n caret.value = position\n}\n\nfunction commit(next: string, nextCaret: number): void {\n writeField(next, nextCaret)\n\n if (next !== value.value) {\n emit('update:modelValue', next)\n emit('change', next)\n }\n}\n\nfunction onInput(event: Event): void {\n const el = event.target as HTMLInputElement\n const normalized = normalizeOtpInput({\n previous: value.value,\n raw: el.value,\n caret: el.selectionStart ?? el.value.length,\n length: resolvedLength.value,\n matcher: matcher.value,\n uppercase: uppercase.value,\n })\n\n commit(normalized.value, normalized.caret)\n}\n\nfunction syncCaret(): void {\n const el = fieldEl.value\n if (el)\n caret.value = el.selectionStart ?? 0\n}\n\n/**\n * Клик ставит каретку в первую пустую ячейку, а не туда, куда пришёлся курсор.\n *\n * Ячейки — декорация: попадание мышью по невидимому тексту ничего осмысленного\n * не значит, а «продолжить с того места, где остановился» — единственное, чего\n * от кода ждут. Редактирование середины остаётся за стрелками.\n */\nfunction onPointerUp(): void {\n const el = fieldEl.value\n if (!el || isDisabled.value || isReadonly.value)\n return\n\n const position = value.value.length\n el.setSelectionRange(position, position)\n caret.value = position\n}\n\nfunction onFocus(event: FocusEvent): void {\n focused.value = true\n syncCaret()\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent): void {\n focused.value = false\n emit('blur', event)\n}\n\nfunction focus(): void {\n fieldEl.value?.focus()\n}\n\nfunction blur(): void {\n fieldEl.value?.blur()\n}\n\n/** Штатный сценарий: сервер отверг код, поле чистится и снова ждёт ввода. */\nfunction clear(): void {\n commit('', 0)\n focus()\n}\n\ndefineExpose({ focus, blur, clear })\n\n// Значение пришло снаружи — поле обязано его показать. Санитайз уже прошёл в\n// `value`, поэтому сюда доезжает то же, что видят ячейки.\nwatch(value, (next) => {\n const el = fieldEl.value\n if (el && el.value !== next)\n writeField(next, next.length)\n}, { flush: 'post' })\n\n/**\n * Повторный `complete` на том же значении не шлётся: правка последней ячейки\n * туда-обратно не должна вызывать второй запрос на проверку кода.\n */\nconst completed = ref<string | null>(null)\n\nwatch(value, (next) => {\n if (next.length < resolvedLength.value) {\n completed.value = null\n return\n }\n\n if (completed.value === next)\n return\n\n completed.value = next\n emit('complete', next)\n}, { immediate: true })\n\nlet otpRequest: AbortController | undefined\n\nfunction abortWebOtp(): void {\n otpRequest?.abort()\n otpRequest = undefined\n}\n\n/**\n * WebOTP — второй канал автоподстановки, закрывающий Android так же, как\n * `one-time-code` закрывает iOS.\n *\n * Живёт в `onMounted`, а не в теле setup: `navigator` на сервере роняет рендер\n * (`docs/ssr.md`). Отказ пользователя, отсутствие API и отмена — не ошибки:\n * поле просто остаётся пустым и ждёт ручного ввода.\n */\nfunction requestWebOtp(): void {\n abortWebOtp()\n\n if (typeof window === 'undefined' || !('OTPCredential' in window))\n return\n\n const controller = new AbortController()\n otpRequest = controller\n\n // WebOTP нет в `lib.dom`, поэтому форма запроса описана здесь: тип узкий и\n // ровно тот, что читается ниже.\n const credentials = navigator.credentials as unknown as {\n get: (options: unknown) => Promise<{ code?: string } | null>\n }\n\n credentials\n .get({ otp: { transport: ['sms'] }, signal: controller.signal })\n .then((credential: { code?: string } | null) => {\n if (otpRequest !== controller || !credential?.code)\n return\n\n const normalized = sanitizeOtpValue(credential.code, {\n length: resolvedLength.value,\n matcher: matcher.value,\n uppercase: uppercase.value,\n })\n\n commit(normalized, normalized.length)\n })\n .catch(() => {})\n}\n\nonMounted(() => {\n if (props.autofocus)\n focus()\n\n if (props.webOtp)\n requestWebOtp()\n})\n\n// Код набран — ждать сообщение больше незачем, а незакрытый запрос переживает\n// уход со страницы.\nwatch(value, (next) => {\n if (next.length >= resolvedLength.value)\n abortWebOtp()\n})\n\nonBeforeUnmount(abortWebOtp)\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (!isOtpGroupsValid(resolvedLength.value, props.groups)) {\n console.warn(\n `[granularity] GrOtpInput: сумма \\`groups\\` не равна \\`length\\` (${resolvedLength.value}) `\n + '— разбивка игнорируется целиком. Половина разбивки хуже её отсутствия: '\n + 'разделитель встал бы не там, где группа кончается.',\n )\n }\n\n if (resolvedLength.value < 1) {\n console.warn('[granularity] GrOtpInput: `length` меньше единицы — рисовать нечего.')\n }\n })\n}\n</script>\n\n<template>\n <div\n data-testid=\"gr-otp-input\"\n data-gr-otp-input\n :class=\"rootClass\"\n >\n <template v-for=\"(char, index) in characters\" :key=\"index\">\n <span\n v-if=\"separators.has(index)\"\n data-gr-otp-input-separator\n :class=\"separatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\" :index=\"index\">—</slot>\n </span>\n\n <span\n data-testid=\"gr-otp-input-cell\"\n data-gr-otp-input-cell\n :class=\"cellClass(index)\"\n aria-hidden=\"true\"\n >\n <slot\n name=\"cell\"\n :char=\"char\"\n :index=\"index\"\n :active=\"index === activeIndex\"\n :filled=\"char !== ''\"\n :masked=\"resolvedMasked\"\n >\n <template v-if=\"char\">{{ resolvedMasked ? '•' : char }}</template>\n <span v-else-if=\"placeholder\" :class=\"placeholderClass\">{{ placeholder }}</span>\n </slot>\n\n <span\n v-if=\"index === activeIndex && !char\"\n data-gr-otp-input-caret\n :class=\"caretClass\"\n />\n </span>\n </template>\n\n <input\n :id=\"fieldId\"\n ref=\"fieldEl\"\n data-testid=\"gr-otp-input-field\"\n data-gr-otp-input-field\n type=\"text\"\n :class=\"fieldClass\"\n :value=\"value\"\n :name=\"name\"\n :form=\"form\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :required=\"isRequired\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :inputmode=\"OTP_TYPE_INPUT_MODES[type]\"\n :autocomplete=\"oneTimeCode ? 'one-time-code' : 'off'\"\n autocapitalize=\"none\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n @input=\"onInput\"\n @select=\"syncCaret\"\n @keyup=\"syncCaret\"\n @pointerup=\"onPointerUp\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n </div>\n</template>\n\n<style>\n/*\n * Каретка своя, потому что настоящая скрыта вместе с текстом поля. Мигание —\n * CSS-анимация, а значит подчиняется глобальному клампу движения пакета\n * (`docs/motion.md`) и не требует своего reduce-блока.\n */\n@keyframes gr-otp-input-caret {\n 0%,\n 45% {\n opacity: 1;\n }\n\n 55%,\n 100% {\n opacity: 0;\n }\n}\n\n[data-gr-otp-input-caret] {\n animation: gr-otp-input-caret 1.1s steps(1, end) infinite;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch, watchEffect } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../../composables/useGrComponentConfig'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\n\nimport {\n caretClass,\n fieldClass,\n grOtpInputCellClass,\n grOtpInputRootClass,\n placeholderClass,\n separatorClass,\n type GrOtpInputSize,\n} from './grOtpInputStyles'\nimport {\n createOtpMatcher,\n isOtpGroupsValid,\n normalizeOtpInput,\n OTP_TYPE_INPUT_MODES,\n otpSeparatorIndexes,\n sanitizeOtpValue,\n type GrOtpInputType,\n} from './otpValue'\n\nexport type { GrOtpInputSize } from './grOtpInputStyles'\nexport type { GrOtpInputType } from './otpValue'\n\nexport interface GrOtpInputProps {\n /** Код целиком, без разделителей. */\n modelValue?: string\n /** Число ячеек. */\n length?: number\n /** Допустимый алфавит и раскладка мобильной клавиатуры. */\n type?: GrOtpInputType\n /** Свой алфавит регуляркой на **один символ**. Перекрывает `type`. */\n pattern?: string\n /** Разбивка на группы: `[3, 3]` рисует `123 – 456`. Сумма обязана равняться `length`. */\n groups?: number[]\n /** Точки вместо символов — сценарий PIN. На `modelValue` не влияет. */\n masked?: boolean\n /** Знак в пустой ячейке. */\n placeholder?: string\n size?: GrOtpInputSize\n autofocus?: boolean\n /** `autocomplete=\"one-time-code\"`: код из SMS подставляется на iOS и macOS. */\n oneTimeCode?: boolean\n /**\n * Приём кода из SMS на Android через WebOTP. Выключен: требует HTTPS и\n * особого хвоста в тексте сообщения, который добавляет бэкенд.\n */\n webOtp?: boolean\n /** Имя поля для нативной отправки формы. */\n name?: string\n /** `id` формы, если поле лежит вне неё. */\n form?: string\n disabled?: boolean\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrOtpInputEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /** Код набран целиком. Точка автосабмита. */\n (e: 'complete', value: string): void\n}\n\nconst props = withDefaults(\n defineProps<GrOtpInputProps>(),\n {\n modelValue: '',\n // `length`, `size` и `masked` настраиваются через `GrConfigProvider`,\n // поэтому их дефолты живут в резолверах ниже, а не в `withDefaults`.\n length: undefined,\n type: 'numeric',\n pattern: undefined,\n groups: undefined,\n masked: undefined,\n placeholder: '',\n size: undefined,\n autofocus: false,\n oneTimeCode: true,\n webOtp: false,\n name: undefined,\n form: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n },\n)\n\nconst emit = defineEmits<GrOtpInputEmits>()\n\ndefineSlots<{\n /** Своё оформление ячейки. Размер и рамку по-прежнему держит компонент. */\n cell?: (props: {\n char: string\n index: number\n active: boolean\n filled: boolean\n masked: boolean\n }) => any\n /** Знак между группами. По умолчанию — тире. */\n separator?: (props: { index: number }) => any\n}>()\n\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n id: fieldId,\n describedBy,\n} = useGrFormControl(() => props)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrOtpInput' })\nconst resolvedLength = useGrComponentProp('GrOtpInput', 'length', () => props.length, 6)\nconst resolvedMasked = useGrComponentProp('GrOtpInput', 'masked', () => props.masked, false)\n\nconst fieldEl = ref<HTMLInputElement | null>(null)\nconst caret = ref(0)\nconst focused = ref(false)\n\nconst matcher = computed(() => createOtpMatcher(props.type, props.pattern))\n\n/** Коды печатают капсом, и раскладка «то ли a, то ли A» тут только мешает. */\nconst uppercase = computed(() => props.type === 'alphanumeric' && props.pattern === undefined)\n\nconst value = computed(() => sanitizeOtpValue(props.modelValue, {\n length: resolvedLength.value,\n matcher: matcher.value,\n uppercase: uppercase.value,\n}))\n\nconst characters = computed(() => {\n const chars = value.value.split('')\n return Array.from({ length: resolvedLength.value }, (_, index) => chars[index] ?? '')\n})\n\nconst separators = computed(() => otpSeparatorIndexes(resolvedLength.value, props.groups))\n\nconst activeIndex = computed(() => {\n if (!focused.value || isDisabled.value || isReadonly.value)\n return -1\n\n return Math.min(caret.value, resolvedLength.value - 1)\n})\n\nconst rootClass = computed(() => grOtpInputRootClass(resolvedSize.value))\n\nfunction cellClass(index: number): string {\n return grOtpInputCellClass({\n size: resolvedSize.value,\n active: index === activeIndex.value,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n}\n\n/**\n * Значение живёт в пропе, а поле — в DOM, и после каждой правки их надо свести:\n * браузер уже применил свою вставку, а нормализация могла дать другую строку.\n */\nfunction writeField(next: string, nextCaret: number): void {\n const el = fieldEl.value\n if (!el)\n return\n\n if (el.value !== next)\n el.value = next\n\n const position = Math.min(nextCaret, next.length)\n el.setSelectionRange(position, position)\n caret.value = position\n}\n\nfunction commit(next: string, nextCaret: number): void {\n writeField(next, nextCaret)\n\n if (next !== value.value) {\n emit('update:modelValue', next)\n emit('change', next)\n }\n}\n\nfunction onInput(event: Event): void {\n const el = event.target as HTMLInputElement\n const normalized = normalizeOtpInput({\n previous: value.value,\n raw: el.value,\n caret: el.selectionStart ?? el.value.length,\n length: resolvedLength.value,\n matcher: matcher.value,\n uppercase: uppercase.value,\n })\n\n commit(normalized.value, normalized.caret)\n}\n\nfunction syncCaret(): void {\n const el = fieldEl.value\n if (el)\n caret.value = el.selectionStart ?? 0\n}\n\n/**\n * Клик ставит каретку в первую пустую ячейку, а не туда, куда пришёлся курсор.\n *\n * Ячейки — декорация: попадание мышью по невидимому тексту ничего осмысленного\n * не значит, а «продолжить с того места, где остановился» — единственное, чего\n * от кода ждут. Редактирование середины остаётся за стрелками.\n */\nfunction onPointerUp(): void {\n const el = fieldEl.value\n if (!el || isDisabled.value || isReadonly.value)\n return\n\n const position = value.value.length\n el.setSelectionRange(position, position)\n caret.value = position\n}\n\nfunction onFocus(event: FocusEvent): void {\n focused.value = true\n syncCaret()\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent): void {\n focused.value = false\n emit('blur', event)\n}\n\nfunction focus(): void {\n fieldEl.value?.focus()\n}\n\nfunction blur(): void {\n fieldEl.value?.blur()\n}\n\n/** Штатный сценарий: сервер отверг код, поле чистится и снова ждёт ввода. */\nfunction clear(): void {\n commit('', 0)\n focus()\n}\n\ndefineExpose({ focus, blur, clear })\n\n// Значение пришло снаружи — поле обязано его показать. Санитайз уже прошёл в\n// `value`, поэтому сюда доезжает то же, что видят ячейки.\nwatch(value, (next) => {\n const el = fieldEl.value\n if (el && el.value !== next)\n writeField(next, next.length)\n}, { flush: 'post' })\n\n/**\n * Повторный `complete` на том же значении не шлётся: правка последней ячейки\n * туда-обратно не должна вызывать второй запрос на проверку кода.\n */\nconst completed = ref<string | null>(null)\n\nwatch(value, (next) => {\n if (next.length < resolvedLength.value) {\n completed.value = null\n return\n }\n\n if (completed.value === next)\n return\n\n completed.value = next\n emit('complete', next)\n}, { immediate: true })\n\nlet otpRequest: AbortController | undefined\n\nfunction abortWebOtp(): void {\n otpRequest?.abort()\n otpRequest = undefined\n}\n\n/**\n * WebOTP — второй канал автоподстановки, закрывающий Android так же, как\n * `one-time-code` закрывает iOS.\n *\n * Живёт в `onMounted`, а не в теле setup: `navigator` на сервере роняет рендер\n * (`docs/ssr.md`). Отказ пользователя, отсутствие API и отмена — не ошибки:\n * поле просто остаётся пустым и ждёт ручного ввода.\n */\nfunction requestWebOtp(): void {\n abortWebOtp()\n\n if (typeof window === 'undefined' || !('OTPCredential' in window))\n return\n\n const controller = new AbortController()\n otpRequest = controller\n\n // WebOTP нет в `lib.dom`, поэтому форма запроса описана здесь: тип узкий и\n // ровно тот, что читается ниже.\n const credentials = navigator.credentials as unknown as {\n get: (options: unknown) => Promise<{ code?: string } | null>\n }\n\n credentials\n .get({ otp: { transport: ['sms'] }, signal: controller.signal })\n .then((credential: { code?: string } | null) => {\n if (otpRequest !== controller || !credential?.code)\n return\n\n const normalized = sanitizeOtpValue(credential.code, {\n length: resolvedLength.value,\n matcher: matcher.value,\n uppercase: uppercase.value,\n })\n\n commit(normalized, normalized.length)\n })\n .catch(() => {})\n}\n\nonMounted(() => {\n if (props.autofocus)\n focus()\n\n if (props.webOtp)\n requestWebOtp()\n})\n\n// Код набран — ждать сообщение больше незачем, а незакрытый запрос переживает\n// уход со страницы.\nwatch(value, (next) => {\n if (next.length >= resolvedLength.value)\n abortWebOtp()\n})\n\nonBeforeUnmount(abortWebOtp)\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (!isOtpGroupsValid(resolvedLength.value, props.groups)) {\n console.warn(\n `[granularity] GrOtpInput: сумма \\`groups\\` не равна \\`length\\` (${resolvedLength.value}) `\n + '— разбивка игнорируется целиком. Половина разбивки хуже её отсутствия: '\n + 'разделитель встал бы не там, где группа кончается.',\n )\n }\n\n if (resolvedLength.value < 1) {\n console.warn('[granularity] GrOtpInput: `length` меньше единицы — рисовать нечего.')\n }\n })\n}\n</script>\n\n<template>\n <div\n data-testid=\"gr-otp-input\"\n data-gr-otp-input\n :class=\"rootClass\"\n >\n <template v-for=\"(char, index) in characters\" :key=\"index\">\n <span\n v-if=\"separators.has(index)\"\n data-gr-otp-input-separator\n :class=\"separatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\" :index=\"index\">—</slot>\n </span>\n\n <span\n data-testid=\"gr-otp-input-cell\"\n data-gr-otp-input-cell\n :class=\"cellClass(index)\"\n aria-hidden=\"true\"\n >\n <slot\n name=\"cell\"\n :char=\"char\"\n :index=\"index\"\n :active=\"index === activeIndex\"\n :filled=\"char !== ''\"\n :masked=\"resolvedMasked\"\n >\n <template v-if=\"char\">{{ resolvedMasked ? '•' : char }}</template>\n <span v-else-if=\"placeholder\" :class=\"placeholderClass\">{{ placeholder }}</span>\n </slot>\n\n <span\n v-if=\"index === activeIndex && !char\"\n data-gr-otp-input-caret\n :class=\"caretClass\"\n />\n </span>\n </template>\n\n <input\n :id=\"fieldId\"\n ref=\"fieldEl\"\n data-testid=\"gr-otp-input-field\"\n data-gr-otp-input-field\n type=\"text\"\n :class=\"fieldClass\"\n :value=\"value\"\n :name=\"name\"\n :form=\"form\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :required=\"isRequired\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :inputmode=\"OTP_TYPE_INPUT_MODES[type]\"\n :autocomplete=\"oneTimeCode ? 'one-time-code' : 'off'\"\n autocapitalize=\"none\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n @input=\"onInput\"\n @select=\"syncCaret\"\n @keyup=\"syncCaret\"\n @pointerup=\"onPointerUp\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n </div>\n</template>\n\n<style>\n/*\n * Каретка своя, потому что настоящая скрыта вместе с текстом поля. Мигание —\n * CSS-анимация, а значит подчиняется глобальному клампу движения пакета\n * (`docs/motion.md`) и не требует своего reduce-блока.\n */\n@keyframes gr-otp-input-caret {\n 0%,\n 45% {\n opacity: 1;\n }\n\n 55%,\n 100% {\n opacity: 0;\n }\n}\n\n[data-gr-otp-input-caret] {\n animation: gr-otp-input-caret 1.1s steps(1, end) infinite;\n}\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { n as grOtpInputSafelist, t as grOtpInputConfig } from "../../chunks/config-BBtBQE3s.js";
2
+ import { t as GrOtpInput_default } from "./chunks/GrOtpInput-B_Q-RDLw.js";
3
+ export { GrOtpInput_default as GrOtpInput, GrOtpInput_default as default, grOtpInputConfig, grOtpInputSafelist };
@@ -0,0 +1 @@
1
+ @keyframes gr-otp-input-caret{0%,45%{opacity:1}55%,to{opacity:0}}[data-gr-otp-input-caret]{animation:1.1s step-end infinite gr-otp-input-caret}
@@ -1,7 +1,7 @@
1
1
  import { a as useGrComponentSize } from "../../../chunks/context-CPMRfwVr.js";
2
2
  import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
3
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-CS4TqMtG.js";
4
- import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-0iI4wdR_.js";
4
+ import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-DvSq-nV-.js";
5
5
  import { a as labelSizes, c as pageListGaps, i as jumperSizes, l as pageSizeSelectWidths, o as navButtonSizes, r as ellipsisSizes, s as pageBoxSizes, u as rowGaps } from "../../../chunks/config-Bdf1bt-m.js";
6
6
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useAttrs, vModelText, watch, watchEffect, withCtx, withDirectives, withKeys } from "vue";
7
7
  //#region src/components/GrPagination/GrPagination.vue?vue&type=script&setup=true&lang.ts