cursedbelt 5.79.1 → 5.79.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/components/OtpInput.d.ts.map +1 -1
- package/dist/react/components/OtpInput.js +15 -5
- package/dist/react/components/OtpInput.js.map +1 -1
- package/dist/react/components/SelectFieldImpl.d.ts.map +1 -1
- package/dist/react/components/SelectFieldImpl.js +8 -1
- package/dist/react/components/SelectFieldImpl.js.map +1 -1
- package/package.json +1 -1
- package/src/react/components/OtpInput.spec.tsx +43 -1
- package/src/react/components/OtpInput.tsx +15 -6
- package/src/react/components/SelectField.spec.tsx +50 -1
- package/src/react/components/SelectFieldImpl.tsx +6 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OtpInput.d.ts","sourceRoot":"","sources":["../../../src/react/components/OtpInput.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;+DAC2D;IAC3D,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,MAAU,EACV,UAAU,EACV,SAAqB,EACrB,QAAQ,EACR,SAAS,EACT,EAAE,EACF,IAAI,EACJ,YAAY,EAAE,SAAS,EACvB,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,SAAS,GACV,EAAE,aAAa,+
|
|
1
|
+
{"version":3,"file":"OtpInput.d.ts","sourceRoot":"","sources":["../../../src/react/components/OtpInput.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;+DAC2D;IAC3D,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,MAAU,EACV,UAAU,EACV,SAAqB,EACrB,QAAQ,EACR,SAAS,EACT,EAAE,EACF,IAAI,EACJ,YAAY,EAAE,SAAS,EACvB,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,SAAS,GACV,EAAE,aAAa,+BA+Gf"}
|
|
@@ -31,8 +31,11 @@ export function OtpInput({ value, onChange, length = 6, onComplete, inputMode =
|
|
|
31
31
|
refs.current[clamped]?.focus();
|
|
32
32
|
refs.current[clamped]?.select();
|
|
33
33
|
};
|
|
34
|
-
const handleChange = (index, raw) => {
|
|
35
|
-
|
|
34
|
+
const handleChange = (index, raw, typed) => {
|
|
35
|
+
// A key typed into a box that already holds a character arrives APPENDED to it ("47"),
|
|
36
|
+
// since no box is capped at one: the key is the event's own `data`. An autofill or a
|
|
37
|
+
// replacement carries the whole code in the value.
|
|
38
|
+
const clean = sanitize(typed ?? raw);
|
|
36
39
|
if (!clean) {
|
|
37
40
|
// A deletion/clear of this box: drop just this position.
|
|
38
41
|
if (chars[index])
|
|
@@ -40,8 +43,9 @@ export function OtpInput({ value, onChange, length = 6, onComplete, inputMode =
|
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
42
45
|
// Typing (or autofilling) writes from this box forward, so a multi-char autofill
|
|
43
|
-
// or a fast typist never loses characters.
|
|
44
|
-
|
|
46
|
+
// or a fast typist never loses characters. The boxes after what was written keep
|
|
47
|
+
// theirs: correcting the third digit of a full code is not retyping the last three.
|
|
48
|
+
const merged = (chars.slice(0, index).join('') + clean + chars.slice(index + clean.length).join('')).slice(0, length);
|
|
45
49
|
commit(merged);
|
|
46
50
|
focusBox(index + clean.length);
|
|
47
51
|
};
|
|
@@ -79,6 +83,12 @@ export function OtpInput({ value, onChange, length = 6, onComplete, inputMode =
|
|
|
79
83
|
refs.current[i] = el;
|
|
80
84
|
}, id: i === 0 ? baseId : `${baseId}-${i}`, name: i === 0 ? name : undefined, type: 'text', inputMode: inputMode, autoComplete: i === 0 ? 'one-time-code' : 'off',
|
|
81
85
|
// biome-ignore lint/a11y/noAutofocus: opt-in via prop; first box only
|
|
82
|
-
autoFocus: autoFocus && i === 0,
|
|
86
|
+
autoFocus: autoFocus && i === 0,
|
|
87
|
+
// 🔴 Never 1: a browser that honours `maxLength` on autofill (iOS Safari's "From
|
|
88
|
+
// Messages" chip) would hand the code box one digit. `commit` is the limit.
|
|
89
|
+
maxLength: length, disabled: disabled, value: char, "aria-label": ariaLabel ? `${ariaLabel} digit ${i + 1}` : `Digit ${i + 1}`, "aria-invalid": ariaInvalid, "aria-describedby": i === 0 ? ariaDescribedBy : undefined, onChange: (e) => {
|
|
90
|
+
const input = e.nativeEvent;
|
|
91
|
+
handleChange(i, e.target.value, input.inputType === 'insertText' && input.data ? input.data : null);
|
|
92
|
+
}, onKeyDown: (e) => handleKeyDown(i, e), onPaste: (e) => handlePaste(i, e), onFocus: (e) => e.currentTarget.select(), className: BOX_CLASS }, i))) }));
|
|
83
93
|
}
|
|
84
94
|
//# sourceMappingURL=OtpInput.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OtpInput.js","sourceRoot":"","sources":["../../../src/react/components/OtpInput.tsx"],"names":[],"mappings":";AAAA,OAAO,EAGL,WAAW,EACX,KAAK,EACL,OAAO,EACP,MAAM,GACP,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAuBlC,MAAM,SAAS,GACb,gZAAgZ,CAAC;AAEnZ;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,MAAM,GAAG,CAAC,EACV,UAAU,EACV,SAAS,GAAG,SAAS,EACrB,QAAQ,EACR,SAAS,EACT,EAAE,EACF,IAAI,EACJ,YAAY,EAAE,SAAS,EACvB,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,SAAS,GACK;IACd,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IACxB,MAAM,MAAM,GAAG,EAAE,IAAI,OAAO,CAAC;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAiC,EAAE,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAE/F,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAC5F,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,IAAY,EAAE,EAAE;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACtC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClB,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC,EACD,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAC/B,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAE;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,GAAW,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"OtpInput.js","sourceRoot":"","sources":["../../../src/react/components/OtpInput.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectFieldImpl.d.ts","sourceRoot":"","sources":["../../../src/react/components/SelectFieldImpl.tsx"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,KAAK,EACL,aAAa,EACb,OAAY,EACZ,MAAW,EACX,WAAuB,EACvB,QAAQ,EACR,UAAiB,EACjB,IAAW,EACX,EAAE,EACF,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,WAAmB,EACnB,YAAoB,GACrB,EAAE,oBAAoB,+
|
|
1
|
+
{"version":3,"file":"SelectFieldImpl.d.ts","sourceRoot":"","sources":["../../../src/react/components/SelectFieldImpl.tsx"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,KAAK,EACL,aAAa,EACb,OAAY,EACZ,MAAW,EACX,WAAuB,EACvB,QAAQ,EACR,UAAiB,EACjB,IAAW,EACX,EAAE,EACF,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,WAAmB,EACnB,YAAoB,GACrB,EAAE,oBAAoB,+BAqGtB"}
|
|
@@ -21,6 +21,13 @@ export default function SelectFieldImpl({ value, onValueChange, options = [], gr
|
|
|
21
21
|
if (initialFocus && !initialOpen)
|
|
22
22
|
triggerRef.current?.focus();
|
|
23
23
|
}, [initialFocus, initialOpen]);
|
|
24
|
-
return (_jsxs(RSelect.Root, { value: value,
|
|
24
|
+
return (_jsxs(RSelect.Root, { value: value,
|
|
25
|
+
// 🔴 "" is never a choice — Radix refuses an item whose value is "". Inside a <form> its
|
|
26
|
+
// hidden native <select> reads "" back for a value code set before that option exists,
|
|
27
|
+
// and reported it here as a pick: collections' Folder picker reset (forge task 3166).
|
|
28
|
+
onValueChange: (next) => {
|
|
29
|
+
if (next !== '')
|
|
30
|
+
onValueChange?.(next);
|
|
31
|
+
}, disabled: disabled, name: name, required: required, defaultOpen: initialOpen, children: [_jsxs(RSelect.Trigger, { ref: triggerRef, id: id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, className: cn(RAW_BUTTON, selectTriggerClass({ fieldStyle, size, className })), children: [_jsx("span", { className: SELECT_VALUE_CLASS, children: _jsx(RSelect.Value, { placeholder: placeholder }) }), _jsx(RSelect.Icon, { asChild: true, children: _jsx(ChevronDown, { className: 'size-4 shrink-0 opacity-50' }) })] }), _jsx(RSelect.Portal, { container: container, children: _jsxs(RSelect.Content, { position: 'popper', sideOffset: 4, collisionPadding: FLOATING_COLLISION_PADDING, className: cn(FLOATING_CONTENT_BASE, FLOATING_ANIMATION, 'max-h-[var(--radix-select-content-available-height)] min-w-[var(--radix-select-trigger-width)] p-1'), children: [_jsx(RSelect.ScrollUpButton, { className: 'flex cursor-default items-center justify-center py-1', children: _jsx(ChevronUp, { className: 'size-4' }) }), _jsxs(RSelect.Viewport, { children: [empty && (_jsxs("div", { className: 'flex flex-col gap-0.5 px-2 py-2.5 text-left', children: [_jsx("span", { className: 'text-sm font-medium', children: emptyLabel ?? 'Nothing to choose from yet' }), emptyHint && _jsx("span", { className: 'text-xs text-muted-foreground', children: emptyHint })] })), allGroups.map((group, gi) => (_jsxs(RSelect.Group, { children: [group.label && (_jsx(RSelect.Label, { className: 'px-2 py-1.5 text-xs font-semibold text-muted-foreground', children: group.label })), group.options.map((opt) => (_jsxs(RSelect.Item, { value: opt.value, disabled: opt.disabled, className: cn(FLOATING_ITEM_BASE, 'pr-8'), children: [_jsx(RSelect.ItemIndicator, { className: 'absolute right-2 flex size-4 items-center justify-center', children: _jsx(Check, { className: 'size-3' }) }), _jsx(RSelect.ItemText, { children: opt.label }), opt.description && (_jsx("span", { className: 'text-xs text-muted-foreground', children: opt.description }))] }, opt.value))), gi < allGroups.length - 1 && _jsx(RSelect.Separator, { className: 'my-1 h-px bg-border' })] }, group.label ?? gi)))] }), _jsx(RSelect.ScrollDownButton, { className: 'flex cursor-default items-center justify-center py-1', children: _jsx(ChevronDown, { className: 'size-4' }) })] }) })] }));
|
|
25
32
|
}
|
|
26
33
|
//# sourceMappingURL=SelectFieldImpl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectFieldImpl.js","sourceRoot":"","sources":["../../../src/react/components/SelectFieldImpl.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAElB,cAAc,EACd,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,KAAK,EACL,aAAa,EACb,OAAO,GAAG,EAAE,EACZ,MAAM,GAAG,EAAE,EACX,WAAW,GAAG,SAAS,EACvB,QAAQ,EACR,UAAU,GAAG,IAAI,EACjB,IAAI,GAAG,IAAI,EACX,EAAE,EACF,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,WAAW,GAAG,KAAK,EACnB,YAAY,GAAG,KAAK,GACC;IACrB,MAAM,SAAS,GAAG,cAAc,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IACrE,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAEnD,+FAA+F;IAC/F,wFAAwF;IACxF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,IAAI,CAAC,WAAW;YAAE,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAChE,CAAC,EAAE,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;IAEhC,OAAO,CACL,MAAC,OAAO,CAAC,IAAI,IACX,KAAK,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"SelectFieldImpl.js","sourceRoot":"","sources":["../../../src/react/components/SelectFieldImpl.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.79.
|
|
3
|
+
"version": "5.79.3",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
-
import { cleanup, fireEvent, render } from '@testing-library/react';
|
|
2
|
+
import { act, cleanup, fireEvent, render } from '@testing-library/react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { OtpInput } from './OtpInput.js';
|
|
5
5
|
|
|
@@ -65,6 +65,48 @@ describe('OtpInput', () => {
|
|
|
65
65
|
expect(onComplete).toHaveBeenCalledWith('654321');
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
test('🔴 an autofilled code in the first box fills all six — no box caps it at one digit', () => {
|
|
69
|
+
const onComplete = mock(() => {});
|
|
70
|
+
const { container } = render(<Controlled onComplete={onComplete} />);
|
|
71
|
+
const b = boxes(container);
|
|
72
|
+
// happy-dom does not enforce maxLength on a value set; a phone's autofill does.
|
|
73
|
+
for (const box of b) expect(box.maxLength).toBeGreaterThanOrEqual(6);
|
|
74
|
+
fireEvent.change(b[0], { target: { value: '123456' } });
|
|
75
|
+
expect(val(container)).toBe('123456');
|
|
76
|
+
expect(b.map((x) => x.value)).toEqual(['1', '2', '3', '4', '5', '6']);
|
|
77
|
+
expect(onComplete).toHaveBeenCalledWith('123456');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('a key typed into a box that holds a digit replaces it, not appends to it', () => {
|
|
81
|
+
const { container } = render(<Controlled />);
|
|
82
|
+
const b = boxes(container);
|
|
83
|
+
fireEvent.change(b[0], { target: { value: '1' } });
|
|
84
|
+
fireEvent.change(b[1], { target: { value: '2' } });
|
|
85
|
+
const typed = new InputEvent('input', { bubbles: true, inputType: 'insertText', data: '7' });
|
|
86
|
+
// The browser's own write, under React's value tracker — as a keystroke lands.
|
|
87
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(b[1], '27');
|
|
88
|
+
act(() => {
|
|
89
|
+
b[1].dispatchEvent(typed);
|
|
90
|
+
});
|
|
91
|
+
expect(val(container)).toBe('17');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('🔴 correcting one digit of a full code keeps the digits after it', () => {
|
|
95
|
+
const onComplete = mock(() => {});
|
|
96
|
+
const { container } = render(<Controlled onComplete={onComplete} />);
|
|
97
|
+
const b = boxes(container);
|
|
98
|
+
fireEvent.change(b[0], { target: { value: '123456' } });
|
|
99
|
+
onComplete.mockClear();
|
|
100
|
+
// The box's digit is selected on focus, so the key replaces it: the value is the key alone.
|
|
101
|
+
const typed = new InputEvent('input', { bubbles: true, inputType: 'insertText', data: '9' });
|
|
102
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(b[2], '9');
|
|
103
|
+
act(() => {
|
|
104
|
+
b[2].dispatchEvent(typed);
|
|
105
|
+
});
|
|
106
|
+
expect(val(container)).toBe('129456');
|
|
107
|
+
expect(onComplete).toHaveBeenCalledWith('129456');
|
|
108
|
+
});
|
|
109
|
+
|
|
68
110
|
test('backspace on an empty box clears the previous digit', () => {
|
|
69
111
|
const { container } = render(<Controlled />);
|
|
70
112
|
const b = boxes(container);
|
|
@@ -84,16 +84,20 @@ export function OtpInput({
|
|
|
84
84
|
refs.current[clamped]?.select();
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
const handleChange = (index: number, raw: string) => {
|
|
88
|
-
|
|
87
|
+
const handleChange = (index: number, raw: string, typed: string | null) => {
|
|
88
|
+
// A key typed into a box that already holds a character arrives APPENDED to it ("47"),
|
|
89
|
+
// since no box is capped at one: the key is the event's own `data`. An autofill or a
|
|
90
|
+
// replacement carries the whole code in the value.
|
|
91
|
+
const clean = sanitize(typed ?? raw);
|
|
89
92
|
if (!clean) {
|
|
90
93
|
// A deletion/clear of this box: drop just this position.
|
|
91
94
|
if (chars[index]) commit(chars.slice(0, index).join('') + chars.slice(index + 1).join(''));
|
|
92
95
|
return;
|
|
93
96
|
}
|
|
94
97
|
// Typing (or autofilling) writes from this box forward, so a multi-char autofill
|
|
95
|
-
// or a fast typist never loses characters.
|
|
96
|
-
|
|
98
|
+
// or a fast typist never loses characters. The boxes after what was written keep
|
|
99
|
+
// theirs: correcting the third digit of a full code is not retyping the last three.
|
|
100
|
+
const merged = (chars.slice(0, index).join('') + clean + chars.slice(index + clean.length).join('')).slice(0, length);
|
|
97
101
|
commit(merged);
|
|
98
102
|
focusBox(index + clean.length);
|
|
99
103
|
};
|
|
@@ -145,13 +149,18 @@ export function OtpInput({
|
|
|
145
149
|
autoComplete={i === 0 ? 'one-time-code' : 'off'}
|
|
146
150
|
// biome-ignore lint/a11y/noAutofocus: opt-in via prop; first box only
|
|
147
151
|
autoFocus={autoFocus && i === 0}
|
|
148
|
-
maxLength
|
|
152
|
+
// 🔴 Never 1: a browser that honours `maxLength` on autofill (iOS Safari's "From
|
|
153
|
+
// Messages" chip) would hand the code box one digit. `commit` is the limit.
|
|
154
|
+
maxLength={length}
|
|
149
155
|
disabled={disabled}
|
|
150
156
|
value={char}
|
|
151
157
|
aria-label={ariaLabel ? `${ariaLabel} digit ${i + 1}` : `Digit ${i + 1}`}
|
|
152
158
|
aria-invalid={ariaInvalid}
|
|
153
159
|
aria-describedby={i === 0 ? ariaDescribedBy : undefined}
|
|
154
|
-
onChange={(e) =>
|
|
160
|
+
onChange={(e) => {
|
|
161
|
+
const input = e.nativeEvent as InputEvent;
|
|
162
|
+
handleChange(i, e.target.value, input.inputType === 'insertText' && input.data ? input.data : null);
|
|
163
|
+
}}
|
|
155
164
|
onKeyDown={(e) => handleKeyDown(i, e)}
|
|
156
165
|
onPaste={(e) => handlePaste(i, e)}
|
|
157
166
|
onFocus={(e) => e.currentTarget.select()}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
+
import { Button } from './Button.js';
|
|
6
7
|
import { SelectField } from './SelectField.js';
|
|
7
8
|
|
|
8
9
|
afterEach(cleanup);
|
|
@@ -115,3 +116,51 @@ describe('SelectField (lazy)', () => {
|
|
|
115
116
|
expect(screen.getByRole('option', { name: 'Title' })).toBeTruthy();
|
|
116
117
|
});
|
|
117
118
|
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 🔴 Inside a <form>, Radix renders a hidden native <select> and copies each value into it; a value
|
|
122
|
+
* that is not one of its options yet reads back as "" and Radix reports `onValueChange("")` — which
|
|
123
|
+
* reset collections' Folder picker after an upload into a NEW folder (forge task 3166).
|
|
124
|
+
*/
|
|
125
|
+
describe('SelectField inside a form', () => {
|
|
126
|
+
function Folder({ calls }: { calls: string[] }) {
|
|
127
|
+
const [value, setValue] = useState('title');
|
|
128
|
+
const [options, setOptions] = useState(OPTIONS);
|
|
129
|
+
return (
|
|
130
|
+
<form>
|
|
131
|
+
<SelectField
|
|
132
|
+
aria-label='Folder'
|
|
133
|
+
name='folder'
|
|
134
|
+
value={value}
|
|
135
|
+
onValueChange={(v) => {
|
|
136
|
+
calls.push(v);
|
|
137
|
+
setValue(v);
|
|
138
|
+
}}
|
|
139
|
+
options={options}
|
|
140
|
+
/>
|
|
141
|
+
<Button
|
|
142
|
+
tooltip='Land the upload in a new folder'
|
|
143
|
+
onClick={() => {
|
|
144
|
+
setValue('landed');
|
|
145
|
+
setOptions([...OPTIONS, { value: 'landed', label: 'Landed' }]);
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
land
|
|
149
|
+
</Button>
|
|
150
|
+
<output data-v>{value}</output>
|
|
151
|
+
</form>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
test('a value set by code before its option exists is kept — no onValueChange("")', async () => {
|
|
156
|
+
const calls: string[] = [];
|
|
157
|
+
const { container } = render(<Folder calls={calls} />);
|
|
158
|
+
await act(async () => {
|
|
159
|
+
fireEvent.click(screen.getByRole('button', { name: 'land' }));
|
|
160
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
161
|
+
});
|
|
162
|
+
expect(calls).toEqual([]);
|
|
163
|
+
expect(container.querySelector('[data-v]')?.textContent).toBe('landed');
|
|
164
|
+
expect(screen.getByRole('combobox', { name: 'Folder' }).textContent).toContain('Landed');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -56,7 +56,12 @@ export default function SelectFieldImpl({
|
|
|
56
56
|
return (
|
|
57
57
|
<RSelect.Root
|
|
58
58
|
value={value}
|
|
59
|
-
|
|
59
|
+
// 🔴 "" is never a choice — Radix refuses an item whose value is "". Inside a <form> its
|
|
60
|
+
// hidden native <select> reads "" back for a value code set before that option exists,
|
|
61
|
+
// and reported it here as a pick: collections' Folder picker reset (forge task 3166).
|
|
62
|
+
onValueChange={(next) => {
|
|
63
|
+
if (next !== '') onValueChange?.(next);
|
|
64
|
+
}}
|
|
60
65
|
disabled={disabled}
|
|
61
66
|
name={name}
|
|
62
67
|
required={required}
|