@mantine/hooks 9.0.0-alpha.3 → 9.0.0-alpha.5
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/cjs/index.cjs +2 -0
- package/cjs/index.cjs.map +1 -1
- package/cjs/use-click-outside/use-click-outside.cjs +11 -11
- package/cjs/use-click-outside/use-click-outside.cjs.map +1 -1
- package/cjs/use-collapse/use-collapse.cjs +3 -2
- package/cjs/use-collapse/use-collapse.cjs.map +1 -1
- package/cjs/use-drag/use-drag.cjs +304 -0
- package/cjs/use-drag/use-drag.cjs.map +1 -0
- package/cjs/use-headroom/use-headroom.cjs +60 -42
- package/cjs/use-headroom/use-headroom.cjs.map +1 -1
- package/cjs/use-hotkeys/use-hotkeys.cjs +14 -14
- package/cjs/use-hotkeys/use-hotkeys.cjs.map +1 -1
- package/cjs/use-mask/use-mask.cjs +583 -0
- package/cjs/use-mask/use-mask.cjs.map +1 -0
- package/cjs/use-page-leave/use-page-leave.cjs +3 -2
- package/cjs/use-page-leave/use-page-leave.cjs.map +1 -1
- package/cjs/use-roving-index/use-roving-index.cjs +270 -0
- package/cjs/use-roving-index/use-roving-index.cjs.map +1 -0
- package/cjs/use-scroll-direction/use-scroll-direction.cjs +39 -0
- package/cjs/use-scroll-direction/use-scroll-direction.cjs.map +1 -0
- package/cjs/use-text-selection/use-text-selection.cjs +2 -2
- package/cjs/use-text-selection/use-text-selection.cjs.map +1 -1
- package/cjs/use-window-event/use-window-event.cjs +4 -3
- package/cjs/use-window-event/use-window-event.cjs.map +1 -1
- package/esm/index.mjs +1 -0
- package/esm/index.mjs.map +1 -1
- package/esm/use-click-outside/use-click-outside.mjs +12 -12
- package/esm/use-click-outside/use-click-outside.mjs.map +1 -1
- package/esm/use-collapse/use-collapse.mjs +4 -3
- package/esm/use-collapse/use-collapse.mjs.map +1 -1
- package/esm/use-drag/use-drag.mjs +302 -0
- package/esm/use-drag/use-drag.mjs.map +1 -0
- package/esm/use-headroom/use-headroom.mjs +60 -42
- package/esm/use-headroom/use-headroom.mjs.map +1 -1
- package/esm/use-hotkeys/use-hotkeys.mjs +15 -15
- package/esm/use-hotkeys/use-hotkeys.mjs.map +1 -1
- package/esm/use-mask/use-mask.mjs +577 -0
- package/esm/use-mask/use-mask.mjs.map +1 -0
- package/esm/use-page-leave/use-page-leave.mjs +4 -3
- package/esm/use-page-leave/use-page-leave.mjs.map +1 -1
- package/esm/use-roving-index/use-roving-index.mjs +268 -0
- package/esm/use-roving-index/use-roving-index.mjs.map +1 -0
- package/esm/use-scroll-direction/use-scroll-direction.mjs +37 -0
- package/esm/use-scroll-direction/use-scroll-direction.mjs.map +1 -0
- package/esm/use-text-selection/use-text-selection.mjs +3 -3
- package/esm/use-text-selection/use-text-selection.mjs.map +1 -1
- package/esm/use-window-event/use-window-event.mjs +5 -4
- package/esm/use-window-event/use-window-event.mjs.map +1 -1
- package/lib/index.d.mts +2 -0
- package/lib/index.d.ts +2 -0
- package/lib/use-drag/use-drag.d.ts +60 -0
- package/lib/use-headroom/use-headroom.d.ts +11 -2
- package/lib/use-mask/use-mask.d.ts +60 -0
- package/lib/use-roving-index/use-roving-index.d.ts +49 -0
- package/lib/use-scroll-direction/use-scroll-direction.d.ts +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TOKENS = {
|
|
7
|
+
"9": /[0-9]/,
|
|
8
|
+
a: /[A-Za-z]/,
|
|
9
|
+
A: /[A-Z]/,
|
|
10
|
+
"*": /[A-Za-z0-9]/,
|
|
11
|
+
"#": /[-+0-9]/
|
|
12
|
+
};
|
|
13
|
+
function parseMask(mask, tokens) {
|
|
14
|
+
if (Array.isArray(mask)) {
|
|
15
|
+
return mask.map((item) => {
|
|
16
|
+
if (item instanceof RegExp) {
|
|
17
|
+
return { type: "token", char: "_", pattern: item };
|
|
18
|
+
}
|
|
19
|
+
return { type: "literal", char: item };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const slots = [];
|
|
23
|
+
let optional = false;
|
|
24
|
+
for (let i = 0; i < mask.length; i++) {
|
|
25
|
+
const char = mask[i];
|
|
26
|
+
if (char === "\\" && i + 1 < mask.length) {
|
|
27
|
+
i++;
|
|
28
|
+
slots.push({ type: "literal", char: mask[i] });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === "?") {
|
|
32
|
+
optional = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (tokens[char]) {
|
|
36
|
+
slots.push({ type: "token", char, pattern: tokens[char], optional });
|
|
37
|
+
} else {
|
|
38
|
+
slots.push({ type: "literal", char, optional });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return slots;
|
|
42
|
+
}
|
|
43
|
+
function getSlotChar(slotCharOption, index) {
|
|
44
|
+
if (slotCharOption === null || slotCharOption === "" || slotCharOption === void 0) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
if (slotCharOption.length > 1) {
|
|
48
|
+
return slotCharOption[index] || "_";
|
|
49
|
+
}
|
|
50
|
+
return slotCharOption;
|
|
51
|
+
}
|
|
52
|
+
function applyMaskToRaw(raw, slots, _slotCharOption, transform) {
|
|
53
|
+
let result = "";
|
|
54
|
+
let rawIndex = 0;
|
|
55
|
+
let slotIndex = 0;
|
|
56
|
+
for (slotIndex = 0; slotIndex < slots.length; slotIndex++) {
|
|
57
|
+
const slot = slots[slotIndex];
|
|
58
|
+
if (slot.type === "literal") {
|
|
59
|
+
result += slot.char;
|
|
60
|
+
} else if (rawIndex < raw.length) {
|
|
61
|
+
const ch = transform ? transform(raw[rawIndex]) : raw[rawIndex];
|
|
62
|
+
if (slot.pattern && slot.pattern.test(ch)) {
|
|
63
|
+
result += ch;
|
|
64
|
+
rawIndex++;
|
|
65
|
+
} else {
|
|
66
|
+
rawIndex++;
|
|
67
|
+
slotIndex--;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function buildDisplayValue(value, slots, slotCharOption, showSlots) {
|
|
76
|
+
if (!showSlots) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
let display = value;
|
|
80
|
+
for (let i = value.length; i < slots.length; i++) {
|
|
81
|
+
const slot = slots[i];
|
|
82
|
+
if (slot.type === "literal") {
|
|
83
|
+
display += slot.char;
|
|
84
|
+
} else {
|
|
85
|
+
const sc = getSlotChar(slotCharOption, i);
|
|
86
|
+
if (!sc) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
display += sc;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return display;
|
|
93
|
+
}
|
|
94
|
+
function extractRaw(masked, slots) {
|
|
95
|
+
let raw = "";
|
|
96
|
+
for (let i = 0; i < masked.length && i < slots.length; i++) {
|
|
97
|
+
if (slots[i].type === "token") {
|
|
98
|
+
raw += masked[i];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return raw;
|
|
102
|
+
}
|
|
103
|
+
function checkComplete(masked, slots) {
|
|
104
|
+
for (let i = 0; i < slots.length; i++) {
|
|
105
|
+
if (slots[i].type === "token" && !slots[i].optional) {
|
|
106
|
+
if (i >= masked.length) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (!slots[i].pattern.test(masked[i])) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
function findNextTokenIndex(slots, from) {
|
|
117
|
+
for (let i = from; i < slots.length; i++) {
|
|
118
|
+
if (slots[i].type === "token") {
|
|
119
|
+
return i;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return slots.length;
|
|
123
|
+
}
|
|
124
|
+
function findPrevTokenIndex(slots, from) {
|
|
125
|
+
for (let i = from; i >= 0; i--) {
|
|
126
|
+
if (slots[i].type === "token") {
|
|
127
|
+
return i;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return -1;
|
|
131
|
+
}
|
|
132
|
+
function processInput(inputValue, slots, _slotCharOption) {
|
|
133
|
+
let result = "";
|
|
134
|
+
let inputIndex = 0;
|
|
135
|
+
for (let slotIndex = 0; slotIndex < slots.length && inputIndex <= inputValue.length; slotIndex++) {
|
|
136
|
+
const slot = slots[slotIndex];
|
|
137
|
+
if (slot.type === "literal") {
|
|
138
|
+
result += slot.char;
|
|
139
|
+
if (inputIndex < inputValue.length && inputValue[inputIndex] === slot.char) {
|
|
140
|
+
inputIndex++;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (inputIndex >= inputValue.length) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
while (inputIndex < inputValue.length) {
|
|
148
|
+
const ch = inputValue[inputIndex];
|
|
149
|
+
inputIndex++;
|
|
150
|
+
if (slot.pattern.test(ch)) {
|
|
151
|
+
result += ch;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (result.length <= slotIndex) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function getResolvedOptions(options, rawValue) {
|
|
162
|
+
const tokens = { ...DEFAULT_TOKENS, ...options.tokens };
|
|
163
|
+
let mask = options.mask;
|
|
164
|
+
let slotChar = options.slotChar === void 0 ? "_" : options.slotChar;
|
|
165
|
+
let separate = options.separate ?? false;
|
|
166
|
+
if (options.modify) {
|
|
167
|
+
const overrides = options.modify(rawValue);
|
|
168
|
+
if (overrides) {
|
|
169
|
+
if (overrides.mask !== void 0) {
|
|
170
|
+
mask = overrides.mask;
|
|
171
|
+
}
|
|
172
|
+
if (overrides.tokens !== void 0) {
|
|
173
|
+
Object.assign(tokens, overrides.tokens);
|
|
174
|
+
}
|
|
175
|
+
if (overrides.slotChar !== void 0) {
|
|
176
|
+
slotChar = overrides.slotChar;
|
|
177
|
+
}
|
|
178
|
+
if (overrides.separate !== void 0) {
|
|
179
|
+
separate = overrides.separate;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const slots = parseMask(mask, tokens);
|
|
184
|
+
return { slots, slotChar, separate, tokens, transform: options.transform };
|
|
185
|
+
}
|
|
186
|
+
function formatMask(raw, options) {
|
|
187
|
+
const { slots, slotChar, transform } = getResolvedOptions(options, raw);
|
|
188
|
+
return applyMaskToRaw(raw, slots, slotChar, transform);
|
|
189
|
+
}
|
|
190
|
+
function unformatMask(masked, options) {
|
|
191
|
+
const { slots } = getResolvedOptions(options, "");
|
|
192
|
+
return extractRaw(masked, slots);
|
|
193
|
+
}
|
|
194
|
+
function isMaskComplete(masked, options) {
|
|
195
|
+
const { slots } = getResolvedOptions(options, "");
|
|
196
|
+
return checkComplete(masked, slots);
|
|
197
|
+
}
|
|
198
|
+
function generatePattern(mode, options) {
|
|
199
|
+
const { slots } = getResolvedOptions(options, "");
|
|
200
|
+
let pattern = "";
|
|
201
|
+
for (const slot of slots) {
|
|
202
|
+
if (slot.type === "literal") {
|
|
203
|
+
pattern += slot.char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
204
|
+
} else {
|
|
205
|
+
const src = slot.pattern.source;
|
|
206
|
+
if (mode === "full-inexact") {
|
|
207
|
+
pattern += slot.optional ? `${src}?` : src;
|
|
208
|
+
} else {
|
|
209
|
+
pattern += slot.optional ? `(${src})?` : `(${src})`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return pattern;
|
|
214
|
+
}
|
|
215
|
+
function useMask(options) {
|
|
216
|
+
const optionsRef = react.useRef(options);
|
|
217
|
+
optionsRef.current = options;
|
|
218
|
+
const inputRef = react.useRef(null);
|
|
219
|
+
const [maskedValue, setMaskedValue] = react.useState("");
|
|
220
|
+
const [rawValue, setRawValue] = react.useState("");
|
|
221
|
+
const processedRef = react.useRef("");
|
|
222
|
+
const wasCompleteRef = react.useRef(false);
|
|
223
|
+
const isFocusedRef = react.useRef(false);
|
|
224
|
+
const getOptions = react.useCallback(() => {
|
|
225
|
+
const opts = optionsRef.current;
|
|
226
|
+
return getResolvedOptions(opts, rawValue);
|
|
227
|
+
}, [rawValue]);
|
|
228
|
+
const updateValue = react.useCallback(
|
|
229
|
+
(newMasked, cursorPos) => {
|
|
230
|
+
const opts = optionsRef.current;
|
|
231
|
+
const { slots } = getResolvedOptions(
|
|
232
|
+
opts,
|
|
233
|
+
extractRaw(newMasked, getResolvedOptions(opts, "").slots)
|
|
234
|
+
);
|
|
235
|
+
const raw = extractRaw(newMasked, slots);
|
|
236
|
+
const { slots: resolvedSlots, slotChar } = getResolvedOptions(opts, raw);
|
|
237
|
+
const reprocessed = processInput(newMasked, resolvedSlots);
|
|
238
|
+
const newRaw = extractRaw(reprocessed, resolvedSlots);
|
|
239
|
+
const showSlots = opts.alwaysShowMask || isFocusedRef.current;
|
|
240
|
+
const showOnFocus = opts.showMaskOnFocus !== false;
|
|
241
|
+
const shouldShowSlots = showSlots && (showOnFocus || reprocessed.length > 0);
|
|
242
|
+
const displayValue = buildDisplayValue(reprocessed, resolvedSlots, slotChar, shouldShowSlots);
|
|
243
|
+
processedRef.current = reprocessed;
|
|
244
|
+
setMaskedValue(displayValue);
|
|
245
|
+
setRawValue(newRaw);
|
|
246
|
+
if (inputRef.current) {
|
|
247
|
+
inputRef.current.value = displayValue;
|
|
248
|
+
if (cursorPos !== void 0 && document.activeElement === inputRef.current) {
|
|
249
|
+
const pos = Math.min(cursorPos, reprocessed.length);
|
|
250
|
+
inputRef.current.setSelectionRange(pos, pos);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (opts.onChangeRaw) {
|
|
254
|
+
opts.onChangeRaw(newRaw, displayValue);
|
|
255
|
+
}
|
|
256
|
+
const complete = checkComplete(reprocessed, resolvedSlots);
|
|
257
|
+
if (complete && !wasCompleteRef.current && opts.onComplete) {
|
|
258
|
+
opts.onComplete(displayValue, newRaw);
|
|
259
|
+
}
|
|
260
|
+
wasCompleteRef.current = complete;
|
|
261
|
+
return { displayValue, newRaw, reprocessed, resolvedSlots };
|
|
262
|
+
},
|
|
263
|
+
[getOptions]
|
|
264
|
+
);
|
|
265
|
+
const handleInput = react.useCallback(
|
|
266
|
+
(e) => {
|
|
267
|
+
const input = e.target;
|
|
268
|
+
const opts = optionsRef.current;
|
|
269
|
+
const { slots: resolvedSlots, slotChar, transform } = getResolvedOptions(opts, "");
|
|
270
|
+
const raw = extractRaw(input.value, resolvedSlots);
|
|
271
|
+
const reformatted = applyMaskToRaw(raw, resolvedSlots, slotChar, transform);
|
|
272
|
+
updateValue(reformatted, reformatted.length);
|
|
273
|
+
},
|
|
274
|
+
[updateValue]
|
|
275
|
+
);
|
|
276
|
+
const clampCursorToProcessed = react.useCallback((input) => {
|
|
277
|
+
const opts = optionsRef.current;
|
|
278
|
+
const { slots } = getResolvedOptions(opts, "");
|
|
279
|
+
const processed = processedRef.current;
|
|
280
|
+
const cursorPos = input.selectionStart ?? 0;
|
|
281
|
+
const maxPos = processed.length > 0 ? findNextEditablePosition(processed.length, slots, processed) : findNextTokenIndex(slots, 0);
|
|
282
|
+
if (cursorPos > maxPos) {
|
|
283
|
+
input.setSelectionRange(maxPos, maxPos);
|
|
284
|
+
}
|
|
285
|
+
}, []);
|
|
286
|
+
const handleFocus = react.useCallback(() => {
|
|
287
|
+
isFocusedRef.current = true;
|
|
288
|
+
const opts = optionsRef.current;
|
|
289
|
+
const input = inputRef.current;
|
|
290
|
+
if (!input) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const { slots, slotChar } = getResolvedOptions(opts, "");
|
|
294
|
+
const showOnFocus = opts.showMaskOnFocus !== false;
|
|
295
|
+
const processed = processedRef.current;
|
|
296
|
+
if (showOnFocus || opts.alwaysShowMask) {
|
|
297
|
+
const display = buildDisplayValue(processed, slots, slotChar, true);
|
|
298
|
+
input.value = display;
|
|
299
|
+
setMaskedValue(display);
|
|
300
|
+
}
|
|
301
|
+
requestAnimationFrame(() => {
|
|
302
|
+
if (input === document.activeElement) {
|
|
303
|
+
clampCursorToProcessed(input);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}, [clampCursorToProcessed]);
|
|
307
|
+
const handleMouseUp = react.useCallback(() => {
|
|
308
|
+
const input = inputRef.current;
|
|
309
|
+
if (!input || input !== document.activeElement) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
clampCursorToProcessed(input);
|
|
313
|
+
}, [clampCursorToProcessed]);
|
|
314
|
+
const handleBlur = react.useCallback(() => {
|
|
315
|
+
isFocusedRef.current = false;
|
|
316
|
+
const opts = optionsRef.current;
|
|
317
|
+
const input = inputRef.current;
|
|
318
|
+
if (!input) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const { slots, slotChar } = getResolvedOptions(opts, rawValue);
|
|
322
|
+
const processed = processInput(input.value, slots);
|
|
323
|
+
const complete = checkComplete(processed, slots);
|
|
324
|
+
if (opts.autoClear && !complete && processed.length > 0) {
|
|
325
|
+
input.value = "";
|
|
326
|
+
processedRef.current = "";
|
|
327
|
+
setMaskedValue("");
|
|
328
|
+
setRawValue("");
|
|
329
|
+
wasCompleteRef.current = false;
|
|
330
|
+
if (opts.onChangeRaw) {
|
|
331
|
+
opts.onChangeRaw("", "");
|
|
332
|
+
}
|
|
333
|
+
if (opts.alwaysShowMask) {
|
|
334
|
+
const emptyDisplay = buildDisplayValue("", slots, slotChar, true);
|
|
335
|
+
input.value = emptyDisplay;
|
|
336
|
+
setMaskedValue(emptyDisplay);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!opts.alwaysShowMask && !complete) {
|
|
341
|
+
const display = buildDisplayValue(processed, slots, slotChar, false);
|
|
342
|
+
input.value = display;
|
|
343
|
+
setMaskedValue(display);
|
|
344
|
+
}
|
|
345
|
+
}, [rawValue]);
|
|
346
|
+
const handleKeyDown = react.useCallback(
|
|
347
|
+
(e) => {
|
|
348
|
+
const input = e.target;
|
|
349
|
+
const opts = optionsRef.current;
|
|
350
|
+
const { slots, slotChar, transform } = getResolvedOptions(opts, rawValue);
|
|
351
|
+
const start = input.selectionStart ?? 0;
|
|
352
|
+
const end = input.selectionEnd ?? 0;
|
|
353
|
+
const processed = processedRef.current;
|
|
354
|
+
if (e.key === "Backspace") {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
if (e.metaKey || e.ctrlKey && !e.altKey) {
|
|
357
|
+
const clampedStart = Math.min(start, processed.length);
|
|
358
|
+
const afterRaw2 = extractRaw(processed.slice(clampedStart), slots.slice(clampedStart));
|
|
359
|
+
const newValue2 = applyMaskToRaw(afterRaw2, slots, slotChar, transform);
|
|
360
|
+
updateValue(newValue2, 0);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (start !== end) {
|
|
364
|
+
const clampedEnd = Math.min(end, processed.length);
|
|
365
|
+
const before = processed.slice(0, start);
|
|
366
|
+
const afterRaw2 = extractRaw(processed.slice(clampedEnd), slots.slice(clampedEnd));
|
|
367
|
+
const newValue2 = applyMaskToRaw(
|
|
368
|
+
extractRaw(before, slots) + afterRaw2,
|
|
369
|
+
slots,
|
|
370
|
+
slotChar,
|
|
371
|
+
transform
|
|
372
|
+
);
|
|
373
|
+
updateValue(newValue2, start);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (start === 0) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
let deletePos = start - 1;
|
|
380
|
+
while (deletePos >= 0 && slots[deletePos] && slots[deletePos].type === "literal") {
|
|
381
|
+
deletePos--;
|
|
382
|
+
}
|
|
383
|
+
if (deletePos < 0) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const beforeRaw = extractRaw(processed.slice(0, deletePos), slots.slice(0, deletePos));
|
|
387
|
+
const afterRaw = extractRaw(processed.slice(deletePos + 1), slots.slice(deletePos + 1));
|
|
388
|
+
const newValue = applyMaskToRaw(beforeRaw + afterRaw, slots, slotChar, transform);
|
|
389
|
+
updateValue(newValue, deletePos);
|
|
390
|
+
} else if (e.key === "Delete") {
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
if (start !== end) {
|
|
393
|
+
const clampedEnd = Math.min(end, processed.length);
|
|
394
|
+
const before = processed.slice(0, start);
|
|
395
|
+
const afterRaw2 = extractRaw(processed.slice(clampedEnd), slots.slice(clampedEnd));
|
|
396
|
+
const newValue2 = applyMaskToRaw(
|
|
397
|
+
extractRaw(before, slots) + afterRaw2,
|
|
398
|
+
slots,
|
|
399
|
+
slotChar,
|
|
400
|
+
transform
|
|
401
|
+
);
|
|
402
|
+
updateValue(newValue2, start);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
let deletePos = start;
|
|
406
|
+
while (deletePos < slots.length && slots[deletePos] && slots[deletePos].type === "literal") {
|
|
407
|
+
deletePos++;
|
|
408
|
+
}
|
|
409
|
+
if (deletePos >= processed.length) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const beforeRaw = extractRaw(processed.slice(0, start), slots.slice(0, start));
|
|
413
|
+
const afterRaw = extractRaw(processed.slice(deletePos + 1), slots.slice(deletePos + 1));
|
|
414
|
+
const newValue = applyMaskToRaw(beforeRaw + afterRaw, slots, slotChar, transform);
|
|
415
|
+
updateValue(newValue, start);
|
|
416
|
+
} else if (e.key === "ArrowRight" && !e.shiftKey) {
|
|
417
|
+
const nextPos = findNextEditablePosition(start + 1, slots, input.value);
|
|
418
|
+
if (nextPos !== start + 1) {
|
|
419
|
+
e.preventDefault();
|
|
420
|
+
input.setSelectionRange(nextPos, nextPos);
|
|
421
|
+
}
|
|
422
|
+
} else if (e.key === "ArrowLeft" && !e.shiftKey) {
|
|
423
|
+
if (start > 0) {
|
|
424
|
+
const prevToken = findPrevTokenIndex(slots, start - 1);
|
|
425
|
+
if (prevToken >= 0 && prevToken !== start - 1) {
|
|
426
|
+
e.preventDefault();
|
|
427
|
+
input.setSelectionRange(prevToken + 1, prevToken + 1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
} else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
431
|
+
e.preventDefault();
|
|
432
|
+
let insertPos = Math.min(start, processed.length);
|
|
433
|
+
while (insertPos < slots.length && slots[insertPos] && slots[insertPos].type === "literal") {
|
|
434
|
+
insertPos++;
|
|
435
|
+
}
|
|
436
|
+
if (insertPos >= slots.length) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const slot = slots[insertPos];
|
|
440
|
+
const ch = transform ? transform(e.key) : e.key;
|
|
441
|
+
if (!slot.pattern.test(ch)) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const beforeRaw = extractRaw(processed.slice(0, insertPos), slots.slice(0, insertPos));
|
|
445
|
+
const afterRaw = start < end ? extractRaw(
|
|
446
|
+
processed.slice(Math.min(end, processed.length)),
|
|
447
|
+
slots.slice(Math.min(end, processed.length))
|
|
448
|
+
) : extractRaw(processed.slice(insertPos), slots.slice(insertPos));
|
|
449
|
+
const newValue = applyMaskToRaw(beforeRaw + ch + afterRaw, slots, slotChar, transform);
|
|
450
|
+
const newCursorPos = findNextEditablePosition(insertPos + 1, slots, newValue);
|
|
451
|
+
updateValue(newValue, newCursorPos);
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
[rawValue, updateValue]
|
|
455
|
+
);
|
|
456
|
+
const handlePaste = react.useCallback(
|
|
457
|
+
(e) => {
|
|
458
|
+
e.preventDefault();
|
|
459
|
+
const input = e.target;
|
|
460
|
+
const opts = optionsRef.current;
|
|
461
|
+
const pastedText = e.clipboardData?.getData("text") ?? "";
|
|
462
|
+
const start = input.selectionStart ?? 0;
|
|
463
|
+
const end = input.selectionEnd ?? 0;
|
|
464
|
+
const processed = processedRef.current;
|
|
465
|
+
const { slots, slotChar, transform } = getResolvedOptions(opts, "");
|
|
466
|
+
const clampedEnd = Math.min(end, processed.length);
|
|
467
|
+
const beforeRaw = extractRaw(processed.slice(0, start), slots.slice(0, start));
|
|
468
|
+
const afterRaw = extractRaw(processed.slice(clampedEnd), slots.slice(clampedEnd));
|
|
469
|
+
const newValue = applyMaskToRaw(
|
|
470
|
+
beforeRaw + pastedText + afterRaw,
|
|
471
|
+
slots,
|
|
472
|
+
slotChar,
|
|
473
|
+
transform
|
|
474
|
+
);
|
|
475
|
+
const { reprocessed } = updateValue(newValue);
|
|
476
|
+
const pasteEndPos = Math.min((reprocessed || newValue).length, slots.length);
|
|
477
|
+
if (input === document.activeElement) {
|
|
478
|
+
input.setSelectionRange(pasteEndPos, pasteEndPos);
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
[updateValue]
|
|
482
|
+
);
|
|
483
|
+
const setAriaAttributes = react.useCallback((input) => {
|
|
484
|
+
const opts = optionsRef.current;
|
|
485
|
+
if (opts.invalid) {
|
|
486
|
+
input.setAttribute("aria-invalid", "true");
|
|
487
|
+
} else {
|
|
488
|
+
input.removeAttribute("aria-invalid");
|
|
489
|
+
}
|
|
490
|
+
}, []);
|
|
491
|
+
const refCallback = react.useCallback(
|
|
492
|
+
(node) => {
|
|
493
|
+
const prevInput = inputRef.current;
|
|
494
|
+
if (prevInput) {
|
|
495
|
+
prevInput.removeEventListener("input", handleInput);
|
|
496
|
+
prevInput.removeEventListener("focus", handleFocus);
|
|
497
|
+
prevInput.removeEventListener("blur", handleBlur);
|
|
498
|
+
prevInput.removeEventListener("mouseup", handleMouseUp);
|
|
499
|
+
prevInput.removeEventListener("keydown", handleKeyDown);
|
|
500
|
+
prevInput.removeEventListener("paste", handlePaste);
|
|
501
|
+
}
|
|
502
|
+
inputRef.current = node;
|
|
503
|
+
if (node) {
|
|
504
|
+
node.addEventListener("input", handleInput);
|
|
505
|
+
node.addEventListener("focus", handleFocus);
|
|
506
|
+
node.addEventListener("blur", handleBlur);
|
|
507
|
+
node.addEventListener("mouseup", handleMouseUp);
|
|
508
|
+
node.addEventListener("keydown", handleKeyDown);
|
|
509
|
+
node.addEventListener("paste", handlePaste);
|
|
510
|
+
setAriaAttributes(node);
|
|
511
|
+
if (options.alwaysShowMask && !node.value) {
|
|
512
|
+
const { slots, slotChar } = getResolvedOptions(options, "");
|
|
513
|
+
const display = buildDisplayValue("", slots, slotChar, true);
|
|
514
|
+
node.value = display;
|
|
515
|
+
setMaskedValue(display);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
[
|
|
520
|
+
handleInput,
|
|
521
|
+
handleFocus,
|
|
522
|
+
handleBlur,
|
|
523
|
+
handleMouseUp,
|
|
524
|
+
handleKeyDown,
|
|
525
|
+
handlePaste,
|
|
526
|
+
setAriaAttributes,
|
|
527
|
+
options
|
|
528
|
+
]
|
|
529
|
+
);
|
|
530
|
+
react.useEffect(() => {
|
|
531
|
+
const input = inputRef.current;
|
|
532
|
+
if (!input) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
setAriaAttributes(input);
|
|
536
|
+
}, [options.invalid, setAriaAttributes]);
|
|
537
|
+
const isComplete = (() => {
|
|
538
|
+
const { slots } = getOptions();
|
|
539
|
+
return checkComplete(processedRef.current, slots);
|
|
540
|
+
})();
|
|
541
|
+
const reset = react.useCallback(() => {
|
|
542
|
+
const opts = optionsRef.current;
|
|
543
|
+
const input = inputRef.current;
|
|
544
|
+
processedRef.current = "";
|
|
545
|
+
setMaskedValue("");
|
|
546
|
+
setRawValue("");
|
|
547
|
+
wasCompleteRef.current = false;
|
|
548
|
+
if (input) {
|
|
549
|
+
if (opts.alwaysShowMask) {
|
|
550
|
+
const { slots, slotChar } = getResolvedOptions(opts, "");
|
|
551
|
+
const display = buildDisplayValue("", slots, slotChar, true);
|
|
552
|
+
input.value = display;
|
|
553
|
+
setMaskedValue(display);
|
|
554
|
+
} else {
|
|
555
|
+
input.value = "";
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (opts.onChangeRaw) {
|
|
559
|
+
opts.onChangeRaw("", "");
|
|
560
|
+
}
|
|
561
|
+
}, []);
|
|
562
|
+
return {
|
|
563
|
+
ref: refCallback,
|
|
564
|
+
value: maskedValue,
|
|
565
|
+
rawValue,
|
|
566
|
+
isComplete,
|
|
567
|
+
reset
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function findNextEditablePosition(from, slots, value) {
|
|
571
|
+
let pos = from;
|
|
572
|
+
while (pos < slots.length && pos < value.length && slots[pos] && slots[pos].type === "literal") {
|
|
573
|
+
pos++;
|
|
574
|
+
}
|
|
575
|
+
return pos;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
exports.formatMask = formatMask;
|
|
579
|
+
exports.generatePattern = generatePattern;
|
|
580
|
+
exports.isMaskComplete = isMaskComplete;
|
|
581
|
+
exports.unformatMask = unformatMask;
|
|
582
|
+
exports.useMask = useMask;
|
|
583
|
+
//# sourceMappingURL=use-mask.cjs.map
|