@arun-dev/headless 4.18.0 → 4.19.1
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/checkbox/index.cjs +38 -20
- package/dist/checkbox/index.js +2 -1
- package/dist/{chunk-IWUVUY76.js → chunk-FDGT4JPK.js} +12 -6
- package/dist/chunk-JJ5FACZU.js +19 -0
- package/dist/otp-input/index.cjs +102 -37
- package/dist/otp-input/index.d.cts +17 -3
- package/dist/otp-input/index.d.ts +17 -3
- package/dist/otp-input/index.js +72 -18
- package/dist/radio-group/index.cjs +43 -27
- package/dist/radio-group/index.js +11 -6
- package/dist/switch/index.cjs +38 -20
- package/dist/switch/index.js +2 -1
- package/package.json +1 -1
package/dist/checkbox/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ __export(index_parts_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/checkbox/CheckboxRoot.tsx
|
|
35
|
-
var
|
|
35
|
+
var import_react6 = require("react");
|
|
36
36
|
|
|
37
37
|
// src/core/useControlled.ts
|
|
38
38
|
var import_react = require("react");
|
|
@@ -68,7 +68,24 @@ function useControlled({
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// src/core/useFormReset.ts
|
|
71
|
+
var import_react3 = require("react");
|
|
72
|
+
|
|
73
|
+
// src/core/useLatest.ts
|
|
71
74
|
var import_react2 = require("react");
|
|
75
|
+
function useLatest(value) {
|
|
76
|
+
const ref = (0, import_react2.useRef)(value);
|
|
77
|
+
(0, import_react2.useLayoutEffect)(() => {
|
|
78
|
+
ref.current = value;
|
|
79
|
+
});
|
|
80
|
+
return ref;
|
|
81
|
+
}
|
|
82
|
+
function afterReset(event, work) {
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
if (!event.defaultPrevented) work();
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/useFormReset.ts
|
|
72
89
|
function useFormReset({
|
|
73
90
|
elementRef,
|
|
74
91
|
inputRef,
|
|
@@ -76,25 +93,26 @@ function useFormReset({
|
|
|
76
93
|
inputChecked,
|
|
77
94
|
commit
|
|
78
95
|
}) {
|
|
79
|
-
const { current: initialValue } = (0,
|
|
80
|
-
(
|
|
96
|
+
const { current: initialValue } = (0, import_react3.useRef)(value);
|
|
97
|
+
const latest = useLatest({ value, inputChecked, commit });
|
|
98
|
+
(0, import_react3.useEffect)(() => {
|
|
81
99
|
const form = elementRef.current?.form;
|
|
82
100
|
if (!form) return;
|
|
83
101
|
function onReset(event) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (inputRef.current) inputRef.current.checked =
|
|
87
|
-
if (
|
|
88
|
-
|
|
102
|
+
afterReset(event, () => {
|
|
103
|
+
const { value: current, inputChecked: checked, commit: set } = latest.current;
|
|
104
|
+
if (inputRef.current) inputRef.current.checked = checked;
|
|
105
|
+
if (current === initialValue) return;
|
|
106
|
+
set(initialValue);
|
|
89
107
|
});
|
|
90
108
|
}
|
|
91
109
|
form.addEventListener("reset", onReset);
|
|
92
110
|
return () => form.removeEventListener("reset", onReset);
|
|
93
|
-
}, [elementRef, inputRef,
|
|
111
|
+
}, [elementRef, inputRef, latest, initialValue]);
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
// src/core/useRender.ts
|
|
97
|
-
var
|
|
115
|
+
var import_react4 = require("react");
|
|
98
116
|
|
|
99
117
|
// src/core/mergeProps.ts
|
|
100
118
|
function isEventHandler(key) {
|
|
@@ -176,17 +194,17 @@ function useRender({
|
|
|
176
194
|
consumerProps
|
|
177
195
|
}) {
|
|
178
196
|
const merged = mergeProps(props, consumerProps);
|
|
179
|
-
if ((0,
|
|
180
|
-
return (0,
|
|
197
|
+
if ((0, import_react4.isValidElement)(render)) {
|
|
198
|
+
return (0, import_react4.cloneElement)(render, mergeProps(merged, render.props));
|
|
181
199
|
}
|
|
182
|
-
return (0,
|
|
200
|
+
return (0, import_react4.createElement)(defaultTagName, merged);
|
|
183
201
|
}
|
|
184
202
|
|
|
185
203
|
// src/checkbox/CheckboxRootContext.ts
|
|
186
|
-
var
|
|
187
|
-
var CheckboxRootContext = (0,
|
|
204
|
+
var import_react5 = require("react");
|
|
205
|
+
var CheckboxRootContext = (0, import_react5.createContext)(null);
|
|
188
206
|
function useCheckboxRootContext() {
|
|
189
|
-
const context = (0,
|
|
207
|
+
const context = (0, import_react5.useContext)(CheckboxRootContext);
|
|
190
208
|
if (context === null) {
|
|
191
209
|
throw new Error("<Checkbox.Indicator> must be rendered inside <Checkbox.Root>.");
|
|
192
210
|
}
|
|
@@ -223,16 +241,16 @@ function CheckboxRoot({
|
|
|
223
241
|
name: "Checkbox.Root",
|
|
224
242
|
state: "checked"
|
|
225
243
|
});
|
|
226
|
-
const commitChecked = (0,
|
|
244
|
+
const commitChecked = (0, import_react6.useCallback)(
|
|
227
245
|
(next) => {
|
|
228
246
|
setChecked(next);
|
|
229
247
|
onCheckedChange?.(next);
|
|
230
248
|
},
|
|
231
249
|
[setChecked, onCheckedChange]
|
|
232
250
|
);
|
|
233
|
-
const state = (0,
|
|
234
|
-
const elementRef = (0,
|
|
235
|
-
const inputRef = (0,
|
|
251
|
+
const state = (0, import_react6.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
252
|
+
const elementRef = (0, import_react6.useRef)(null);
|
|
253
|
+
const inputRef = (0, import_react6.useRef)(null);
|
|
236
254
|
const submitted = checked === true;
|
|
237
255
|
useFormReset({
|
|
238
256
|
elementRef,
|
package/dist/checkbox/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterReset,
|
|
3
|
+
useLatest
|
|
4
|
+
} from "./chunk-JJ5FACZU.js";
|
|
5
|
+
|
|
1
6
|
// src/core/useFormReset.ts
|
|
2
7
|
import { useEffect, useRef } from "react";
|
|
3
8
|
function useFormReset({
|
|
@@ -8,20 +13,21 @@ function useFormReset({
|
|
|
8
13
|
commit
|
|
9
14
|
}) {
|
|
10
15
|
const { current: initialValue } = useRef(value);
|
|
16
|
+
const latest = useLatest({ value, inputChecked, commit });
|
|
11
17
|
useEffect(() => {
|
|
12
18
|
const form = elementRef.current?.form;
|
|
13
19
|
if (!form) return;
|
|
14
20
|
function onReset(event) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (inputRef.current) inputRef.current.checked =
|
|
18
|
-
if (
|
|
19
|
-
|
|
21
|
+
afterReset(event, () => {
|
|
22
|
+
const { value: current, inputChecked: checked, commit: set } = latest.current;
|
|
23
|
+
if (inputRef.current) inputRef.current.checked = checked;
|
|
24
|
+
if (current === initialValue) return;
|
|
25
|
+
set(initialValue);
|
|
20
26
|
});
|
|
21
27
|
}
|
|
22
28
|
form.addEventListener("reset", onReset);
|
|
23
29
|
return () => form.removeEventListener("reset", onReset);
|
|
24
|
-
}, [elementRef, inputRef,
|
|
30
|
+
}, [elementRef, inputRef, latest, initialValue]);
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/core/useLatest.ts
|
|
2
|
+
import { useLayoutEffect, useRef } from "react";
|
|
3
|
+
function useLatest(value) {
|
|
4
|
+
const ref = useRef(value);
|
|
5
|
+
useLayoutEffect(() => {
|
|
6
|
+
ref.current = value;
|
|
7
|
+
});
|
|
8
|
+
return ref;
|
|
9
|
+
}
|
|
10
|
+
function afterReset(event, work) {
|
|
11
|
+
setTimeout(() => {
|
|
12
|
+
if (!event.defaultPrevented) work();
|
|
13
|
+
}, 0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
useLatest,
|
|
18
|
+
afterReset
|
|
19
|
+
};
|
package/dist/otp-input/index.cjs
CHANGED
|
@@ -33,7 +33,7 @@ __export(index_parts_exports, {
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// src/otp-input/OtpInputRoot.tsx
|
|
36
|
-
var
|
|
36
|
+
var import_react5 = require("react");
|
|
37
37
|
|
|
38
38
|
// src/core/useControlled.ts
|
|
39
39
|
var import_react = require("react");
|
|
@@ -68,8 +68,23 @@ function useControlled({
|
|
|
68
68
|
return [value, setValue];
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// src/core/
|
|
71
|
+
// src/core/useLatest.ts
|
|
72
72
|
var import_react2 = require("react");
|
|
73
|
+
function useLatest(value) {
|
|
74
|
+
const ref = (0, import_react2.useRef)(value);
|
|
75
|
+
(0, import_react2.useLayoutEffect)(() => {
|
|
76
|
+
ref.current = value;
|
|
77
|
+
});
|
|
78
|
+
return ref;
|
|
79
|
+
}
|
|
80
|
+
function afterReset(event, work) {
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
if (!event.defaultPrevented) work();
|
|
83
|
+
}, 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/core/useRender.ts
|
|
87
|
+
var import_react3 = require("react");
|
|
73
88
|
|
|
74
89
|
// src/core/mergeProps.ts
|
|
75
90
|
function isEventHandler(key) {
|
|
@@ -151,28 +166,36 @@ function useRender({
|
|
|
151
166
|
consumerProps
|
|
152
167
|
}) {
|
|
153
168
|
const merged = mergeProps(props, consumerProps);
|
|
154
|
-
if ((0,
|
|
155
|
-
return (0,
|
|
169
|
+
if ((0, import_react3.isValidElement)(render)) {
|
|
170
|
+
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
156
171
|
}
|
|
157
|
-
return (0,
|
|
172
|
+
return (0, import_react3.createElement)(defaultTagName, merged);
|
|
158
173
|
}
|
|
159
174
|
|
|
160
175
|
// src/otp-input/OtpInputRootContext.ts
|
|
161
|
-
var
|
|
162
|
-
var OtpInputRootContext = (0,
|
|
176
|
+
var import_react4 = require("react");
|
|
177
|
+
var OtpInputRootContext = (0, import_react4.createContext)(null);
|
|
163
178
|
function useOtpInputRootContext(part) {
|
|
164
|
-
const context = (0,
|
|
179
|
+
const context = (0, import_react4.useContext)(OtpInputRootContext);
|
|
165
180
|
if (context === null) {
|
|
166
181
|
throw new Error(`<OtpInput.${part}> must be rendered inside <OtpInput.Root>.`);
|
|
167
182
|
}
|
|
168
183
|
return context;
|
|
169
184
|
}
|
|
185
|
+
var GAP = " ";
|
|
170
186
|
function sanitize(text, validationType) {
|
|
171
187
|
return validationType === "numeric" ? text.replace(/\D/g, "") : text.replace(/[^A-Za-z0-9]/g, "");
|
|
172
188
|
}
|
|
189
|
+
function normalize(text, validationType, length) {
|
|
190
|
+
const allowed = validationType === "numeric" ? /[^\d ]/g : /[^A-Za-z0-9 ]/g;
|
|
191
|
+
return text.replace(allowed, "").slice(0, length).trimEnd();
|
|
192
|
+
}
|
|
193
|
+
function isComplete(value, length) {
|
|
194
|
+
return value.length === length && !value.includes(GAP);
|
|
195
|
+
}
|
|
173
196
|
function otpInputDataAttributes({ value, length, disabled }) {
|
|
174
197
|
return {
|
|
175
|
-
"data-complete": value
|
|
198
|
+
"data-complete": isComplete(value, length) ? "" : void 0,
|
|
176
199
|
"data-disabled": disabled ? "" : void 0
|
|
177
200
|
};
|
|
178
201
|
}
|
|
@@ -183,6 +206,7 @@ function OtpInputRoot({
|
|
|
183
206
|
value: valueProp,
|
|
184
207
|
defaultValue,
|
|
185
208
|
onValueChange,
|
|
209
|
+
onComplete,
|
|
186
210
|
length = 6,
|
|
187
211
|
validationType = "numeric",
|
|
188
212
|
disabled = false,
|
|
@@ -197,23 +221,24 @@ function OtpInputRoot({
|
|
|
197
221
|
name: "OtpInput.Root",
|
|
198
222
|
state: "value"
|
|
199
223
|
});
|
|
200
|
-
const commit = (0,
|
|
224
|
+
const commit = (0, import_react5.useCallback)(
|
|
201
225
|
(next) => {
|
|
202
|
-
const clean =
|
|
226
|
+
const clean = normalize(next, validationType, length);
|
|
203
227
|
if (clean === value) return;
|
|
204
228
|
setValue(clean);
|
|
205
229
|
onValueChange?.(clean);
|
|
230
|
+
if (isComplete(clean, length)) onComplete?.(clean);
|
|
206
231
|
},
|
|
207
|
-
[validationType, length, value, setValue, onValueChange]
|
|
232
|
+
[validationType, length, value, setValue, onValueChange, onComplete]
|
|
208
233
|
);
|
|
209
|
-
const [selection, setSelection] = (0,
|
|
210
|
-
const inputRef = (0,
|
|
211
|
-
const slotsRef = (0,
|
|
212
|
-
const registerSlot = (0,
|
|
234
|
+
const [selection, setSelection] = (0, import_react5.useState)(null);
|
|
235
|
+
const inputRef = (0, import_react5.useRef)(null);
|
|
236
|
+
const slotsRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
237
|
+
const registerSlot = (0, import_react5.useCallback)((index, element2) => {
|
|
213
238
|
if (element2) slotsRef.current.set(index, element2);
|
|
214
239
|
else slotsRef.current.delete(index);
|
|
215
240
|
}, []);
|
|
216
|
-
const slotAt = (0,
|
|
241
|
+
const slotAt = (0, import_react5.useCallback)((clientX) => {
|
|
217
242
|
let nearest = null;
|
|
218
243
|
let distance = Infinity;
|
|
219
244
|
for (const [index, element2] of slotsRef.current) {
|
|
@@ -226,19 +251,18 @@ function OtpInputRoot({
|
|
|
226
251
|
}
|
|
227
252
|
return nearest;
|
|
228
253
|
}, []);
|
|
229
|
-
const { current: initialValue } = (0,
|
|
230
|
-
|
|
254
|
+
const { current: initialValue } = (0, import_react5.useRef)(value);
|
|
255
|
+
const latest = useLatest(commit);
|
|
256
|
+
(0, import_react5.useEffect)(() => {
|
|
231
257
|
const form = inputRef.current?.form;
|
|
232
258
|
if (!form) return;
|
|
233
259
|
function onReset(event) {
|
|
234
|
-
|
|
235
|
-
if (!event.defaultPrevented) commit(initialValue);
|
|
236
|
-
});
|
|
260
|
+
afterReset(event, () => latest.current(initialValue));
|
|
237
261
|
}
|
|
238
262
|
form.addEventListener("reset", onReset);
|
|
239
263
|
return () => form.removeEventListener("reset", onReset);
|
|
240
|
-
}, [
|
|
241
|
-
const context = (0,
|
|
264
|
+
}, [latest, initialValue]);
|
|
265
|
+
const context = (0, import_react5.useMemo)(
|
|
242
266
|
() => ({
|
|
243
267
|
value,
|
|
244
268
|
length,
|
|
@@ -267,6 +291,7 @@ function OtpInputRoot({
|
|
|
267
291
|
}
|
|
268
292
|
|
|
269
293
|
// src/otp-input/OtpInputInput.tsx
|
|
294
|
+
var import_react6 = require("react");
|
|
270
295
|
function OtpInputInput({ render, ...rest }) {
|
|
271
296
|
const { value, length, disabled, validationType, setSelection, commit, inputRef, slotAt } = useOtpInputRootContext("Input");
|
|
272
297
|
function select(input, index) {
|
|
@@ -279,6 +304,48 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
279
304
|
function report(input) {
|
|
280
305
|
setSelection({ start: input.selectionStart ?? 0, end: input.selectionEnd ?? 0 });
|
|
281
306
|
}
|
|
307
|
+
const pendingCaret = (0, import_react6.useRef)(null);
|
|
308
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
309
|
+
const input = inputRef.current;
|
|
310
|
+
const at = pendingCaret.current;
|
|
311
|
+
pendingCaret.current = null;
|
|
312
|
+
if (input && at !== null && input.ownerDocument.activeElement === input) select(input, at);
|
|
313
|
+
});
|
|
314
|
+
function edit(next, caret) {
|
|
315
|
+
pendingCaret.current = caret;
|
|
316
|
+
commit(next);
|
|
317
|
+
}
|
|
318
|
+
const cleared = (index) => value.slice(0, index) + GAP + value.slice(index + 1);
|
|
319
|
+
function onDelete(event, input) {
|
|
320
|
+
const start = input.selectionStart ?? 0;
|
|
321
|
+
const end = input.selectionEnd ?? start;
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
if (end > start + 1) {
|
|
324
|
+
edit(value.slice(0, start) + GAP.repeat(end - start) + value.slice(end), start);
|
|
325
|
+
} else if (end === start + 1 && value.charAt(start) !== GAP) {
|
|
326
|
+
edit(cleared(start), start);
|
|
327
|
+
} else if (event.inputType.includes("Backward") && start > 0) {
|
|
328
|
+
edit(cleared(start - 1), start - 1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const beforeInput = (0, import_react6.useRef)(() => {
|
|
332
|
+
});
|
|
333
|
+
beforeInput.current = (event) => {
|
|
334
|
+
const input = event.currentTarget;
|
|
335
|
+
if (event.inputType.startsWith("delete") && event.inputType !== "deleteByCut") {
|
|
336
|
+
onDelete(event, input);
|
|
337
|
+
} else if (event.inputType === "insertText") {
|
|
338
|
+
const typed = event.data ?? "";
|
|
339
|
+
if (typed.length > 0 && sanitize(typed, validationType) !== typed) event.preventDefault();
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
(0, import_react6.useEffect)(() => {
|
|
343
|
+
const input = inputRef.current;
|
|
344
|
+
if (!input) return;
|
|
345
|
+
const listener = (event) => beforeInput.current(event);
|
|
346
|
+
input.addEventListener("beforeinput", listener);
|
|
347
|
+
return () => input.removeEventListener("beforeinput", listener);
|
|
348
|
+
}, [inputRef]);
|
|
282
349
|
function syncSelection(event) {
|
|
283
350
|
const input = event.currentTarget;
|
|
284
351
|
const start = input.selectionStart ?? 0;
|
|
@@ -301,12 +368,6 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
301
368
|
maxLength: length,
|
|
302
369
|
pattern: validationType === "numeric" ? `\\d{${length}}` : `[A-Za-z0-9]{${length}}`,
|
|
303
370
|
disabled: disabled || void 0,
|
|
304
|
-
onBeforeInput(event) {
|
|
305
|
-
const typed = event.data;
|
|
306
|
-
if (typed && typed.length === 1 && !sanitize(typed, validationType)) {
|
|
307
|
-
event.preventDefault();
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
371
|
onChange(event) {
|
|
311
372
|
commit(event.currentTarget.value);
|
|
312
373
|
syncSelection(event);
|
|
@@ -317,12 +378,16 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
317
378
|
if (!pasted) return;
|
|
318
379
|
const input = event.currentTarget;
|
|
319
380
|
if (pasted.length >= length) {
|
|
320
|
-
|
|
381
|
+
edit(pasted, length);
|
|
321
382
|
return;
|
|
322
383
|
}
|
|
323
384
|
const start = input.selectionStart ?? value.length;
|
|
324
385
|
const end = input.selectionEnd ?? start;
|
|
325
|
-
|
|
386
|
+
const reach = start + pasted.length;
|
|
387
|
+
edit(
|
|
388
|
+
value.slice(0, start) + pasted + GAP.repeat(Math.max(0, end - reach)) + value.slice(Math.max(end, reach)),
|
|
389
|
+
reach
|
|
390
|
+
);
|
|
326
391
|
},
|
|
327
392
|
onKeyDown(event) {
|
|
328
393
|
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
@@ -333,13 +398,13 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
333
398
|
select(input, event.key === "ArrowLeft" ? at - 1 : at + 1);
|
|
334
399
|
},
|
|
335
400
|
onClick(event) {
|
|
336
|
-
if (event.detail > 1) return;
|
|
337
401
|
const index = slotAt(event.clientX);
|
|
338
402
|
if (index !== null) select(event.currentTarget, index);
|
|
339
403
|
},
|
|
340
404
|
onSelect: syncSelection,
|
|
341
405
|
onFocus(event) {
|
|
342
|
-
|
|
406
|
+
const gap = value.indexOf(GAP);
|
|
407
|
+
select(event.currentTarget, gap === -1 ? value.length : gap);
|
|
343
408
|
},
|
|
344
409
|
onBlur() {
|
|
345
410
|
setSelection(null);
|
|
@@ -350,15 +415,15 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
350
415
|
}
|
|
351
416
|
|
|
352
417
|
// src/otp-input/OtpInputSlot.tsx
|
|
353
|
-
var
|
|
418
|
+
var import_react7 = require("react");
|
|
354
419
|
function OtpInputSlot({ index, className, children, render, ...rest }) {
|
|
355
420
|
const { value, length, disabled, selection, registerSlot } = useOtpInputRootContext("Slot");
|
|
356
|
-
const char = value.charAt(index);
|
|
421
|
+
const char = value.charAt(index) === GAP ? "" : value.charAt(index);
|
|
357
422
|
let active = false;
|
|
358
423
|
if (selection) {
|
|
359
424
|
active = selection.start === selection.end ? index === Math.min(selection.start, length - 1) : index >= selection.start && index < selection.end;
|
|
360
425
|
}
|
|
361
|
-
const ref = (0,
|
|
426
|
+
const ref = (0, import_react7.useCallback)(
|
|
362
427
|
(element) => registerSlot(index, element),
|
|
363
428
|
[registerSlot, index]
|
|
364
429
|
);
|
|
@@ -20,8 +20,18 @@ type OtpInputRootOwnProps = {
|
|
|
20
20
|
value?: string;
|
|
21
21
|
/** Initial value when uncontrolled. Read once, at mount. */
|
|
22
22
|
defaultValue?: string;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Called with the code after every change, partial or complete. A box emptied before the end
|
|
25
|
+
* is a space — `"123 56"` — so check completeness with a pattern such as `/^\d{6}$/`, not
|
|
26
|
+
* the length alone.
|
|
27
|
+
*/
|
|
24
28
|
onValueChange?: (value: string) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Called with the code after a change that leaves it complete: every slot filled, no gap.
|
|
31
|
+
* Typing the last digit, pasting a whole code and filling an emptied slot all count; a change
|
|
32
|
+
* that leaves a slot empty does not. Use it to verify as soon as the code is in.
|
|
33
|
+
*/
|
|
34
|
+
onComplete?: (value: string) => void;
|
|
25
35
|
/** How many characters the code has, and so how many slots it shows. Defaults to 6. */
|
|
26
36
|
length?: number;
|
|
27
37
|
/**
|
|
@@ -48,7 +58,7 @@ type OtpInputRootProps = OtpInputRootOwnProps & Omit<ComponentPropsWithRef<'div'
|
|
|
48
58
|
* Holds the value through `useControlled`. The input reports changes, and the Root keeps only
|
|
49
59
|
* the characters `validationType` allows, up to `length`.
|
|
50
60
|
*/
|
|
51
|
-
declare function OtpInputRoot({ value: valueProp, defaultValue, onValueChange, length, validationType, disabled, className, children, render, ...rest }: OtpInputRootProps): react.JSX.Element;
|
|
61
|
+
declare function OtpInputRoot({ value: valueProp, defaultValue, onValueChange, onComplete, length, validationType, disabled, className, children, render, ...rest }: OtpInputRootProps): react.JSX.Element;
|
|
52
62
|
|
|
53
63
|
type OtpInputInputOwnProps = {
|
|
54
64
|
/** Element to render instead of the default `<input>`. Props and ref are merged onto it. */
|
|
@@ -66,7 +76,11 @@ type OtpInputInputProps = OtpInputInputOwnProps & Omit<ComponentPropsWithRef<'in
|
|
|
66
76
|
*
|
|
67
77
|
* The caret always covers one character, so typing replaces the character in the active slot
|
|
68
78
|
* and moves on, and the last slot of a full code is replaced rather than refused. Left and
|
|
69
|
-
* Right move one slot
|
|
79
|
+
* Right move one slot.
|
|
80
|
+
*
|
|
81
|
+
* Delete and Backspace empty a slot in place: the characters after it keep their slots, and
|
|
82
|
+
* the caret stays, so the next character fills the same slot. Backspace on an empty slot
|
|
83
|
+
* empties the one before it; Delete on an empty slot does nothing.
|
|
70
84
|
*
|
|
71
85
|
* Carries `maxLength` and a `pattern` of exactly `length` allowed characters, so the browser's
|
|
72
86
|
* own validation reports a code that is too short. Both can be overridden.
|
|
@@ -20,8 +20,18 @@ type OtpInputRootOwnProps = {
|
|
|
20
20
|
value?: string;
|
|
21
21
|
/** Initial value when uncontrolled. Read once, at mount. */
|
|
22
22
|
defaultValue?: string;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Called with the code after every change, partial or complete. A box emptied before the end
|
|
25
|
+
* is a space — `"123 56"` — so check completeness with a pattern such as `/^\d{6}$/`, not
|
|
26
|
+
* the length alone.
|
|
27
|
+
*/
|
|
24
28
|
onValueChange?: (value: string) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Called with the code after a change that leaves it complete: every slot filled, no gap.
|
|
31
|
+
* Typing the last digit, pasting a whole code and filling an emptied slot all count; a change
|
|
32
|
+
* that leaves a slot empty does not. Use it to verify as soon as the code is in.
|
|
33
|
+
*/
|
|
34
|
+
onComplete?: (value: string) => void;
|
|
25
35
|
/** How many characters the code has, and so how many slots it shows. Defaults to 6. */
|
|
26
36
|
length?: number;
|
|
27
37
|
/**
|
|
@@ -48,7 +58,7 @@ type OtpInputRootProps = OtpInputRootOwnProps & Omit<ComponentPropsWithRef<'div'
|
|
|
48
58
|
* Holds the value through `useControlled`. The input reports changes, and the Root keeps only
|
|
49
59
|
* the characters `validationType` allows, up to `length`.
|
|
50
60
|
*/
|
|
51
|
-
declare function OtpInputRoot({ value: valueProp, defaultValue, onValueChange, length, validationType, disabled, className, children, render, ...rest }: OtpInputRootProps): react.JSX.Element;
|
|
61
|
+
declare function OtpInputRoot({ value: valueProp, defaultValue, onValueChange, onComplete, length, validationType, disabled, className, children, render, ...rest }: OtpInputRootProps): react.JSX.Element;
|
|
52
62
|
|
|
53
63
|
type OtpInputInputOwnProps = {
|
|
54
64
|
/** Element to render instead of the default `<input>`. Props and ref are merged onto it. */
|
|
@@ -66,7 +76,11 @@ type OtpInputInputProps = OtpInputInputOwnProps & Omit<ComponentPropsWithRef<'in
|
|
|
66
76
|
*
|
|
67
77
|
* The caret always covers one character, so typing replaces the character in the active slot
|
|
68
78
|
* and moves on, and the last slot of a full code is replaced rather than refused. Left and
|
|
69
|
-
* Right move one slot
|
|
79
|
+
* Right move one slot.
|
|
80
|
+
*
|
|
81
|
+
* Delete and Backspace empty a slot in place: the characters after it keep their slots, and
|
|
82
|
+
* the caret stays, so the next character fills the same slot. Backspace on an empty slot
|
|
83
|
+
* empties the one before it; Delete on an empty slot does nothing.
|
|
70
84
|
*
|
|
71
85
|
* Carries `maxLength` and a `pattern` of exactly `length` allowed characters, so the browser's
|
|
72
86
|
* own validation reports a code that is too short. Both can be overridden.
|
package/dist/otp-input/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterReset,
|
|
3
|
+
useLatest
|
|
4
|
+
} from "../chunk-JJ5FACZU.js";
|
|
1
5
|
import {
|
|
2
6
|
useControlled
|
|
3
7
|
} from "../chunk-UEIB3I3T.js";
|
|
@@ -27,12 +31,20 @@ function useOtpInputRootContext(part) {
|
|
|
27
31
|
}
|
|
28
32
|
return context;
|
|
29
33
|
}
|
|
34
|
+
var GAP = " ";
|
|
30
35
|
function sanitize(text, validationType) {
|
|
31
36
|
return validationType === "numeric" ? text.replace(/\D/g, "") : text.replace(/[^A-Za-z0-9]/g, "");
|
|
32
37
|
}
|
|
38
|
+
function normalize(text, validationType, length) {
|
|
39
|
+
const allowed = validationType === "numeric" ? /[^\d ]/g : /[^A-Za-z0-9 ]/g;
|
|
40
|
+
return text.replace(allowed, "").slice(0, length).trimEnd();
|
|
41
|
+
}
|
|
42
|
+
function isComplete(value, length) {
|
|
43
|
+
return value.length === length && !value.includes(GAP);
|
|
44
|
+
}
|
|
33
45
|
function otpInputDataAttributes({ value, length, disabled }) {
|
|
34
46
|
return {
|
|
35
|
-
"data-complete": value
|
|
47
|
+
"data-complete": isComplete(value, length) ? "" : void 0,
|
|
36
48
|
"data-disabled": disabled ? "" : void 0
|
|
37
49
|
};
|
|
38
50
|
}
|
|
@@ -43,6 +55,7 @@ function OtpInputRoot({
|
|
|
43
55
|
value: valueProp,
|
|
44
56
|
defaultValue,
|
|
45
57
|
onValueChange,
|
|
58
|
+
onComplete,
|
|
46
59
|
length = 6,
|
|
47
60
|
validationType = "numeric",
|
|
48
61
|
disabled = false,
|
|
@@ -59,12 +72,13 @@ function OtpInputRoot({
|
|
|
59
72
|
});
|
|
60
73
|
const commit = useCallback(
|
|
61
74
|
(next) => {
|
|
62
|
-
const clean =
|
|
75
|
+
const clean = normalize(next, validationType, length);
|
|
63
76
|
if (clean === value) return;
|
|
64
77
|
setValue(clean);
|
|
65
78
|
onValueChange?.(clean);
|
|
79
|
+
if (isComplete(clean, length)) onComplete?.(clean);
|
|
66
80
|
},
|
|
67
|
-
[validationType, length, value, setValue, onValueChange]
|
|
81
|
+
[validationType, length, value, setValue, onValueChange, onComplete]
|
|
68
82
|
);
|
|
69
83
|
const [selection, setSelection] = useState(null);
|
|
70
84
|
const inputRef = useRef(null);
|
|
@@ -87,17 +101,16 @@ function OtpInputRoot({
|
|
|
87
101
|
return nearest;
|
|
88
102
|
}, []);
|
|
89
103
|
const { current: initialValue } = useRef(value);
|
|
104
|
+
const latest = useLatest(commit);
|
|
90
105
|
useEffect(() => {
|
|
91
106
|
const form = inputRef.current?.form;
|
|
92
107
|
if (!form) return;
|
|
93
108
|
function onReset(event) {
|
|
94
|
-
|
|
95
|
-
if (!event.defaultPrevented) commit(initialValue);
|
|
96
|
-
});
|
|
109
|
+
afterReset(event, () => latest.current(initialValue));
|
|
97
110
|
}
|
|
98
111
|
form.addEventListener("reset", onReset);
|
|
99
112
|
return () => form.removeEventListener("reset", onReset);
|
|
100
|
-
}, [
|
|
113
|
+
}, [latest, initialValue]);
|
|
101
114
|
const context = useMemo(
|
|
102
115
|
() => ({
|
|
103
116
|
value,
|
|
@@ -127,6 +140,7 @@ function OtpInputRoot({
|
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
// src/otp-input/OtpInputInput.tsx
|
|
143
|
+
import { useEffect as useEffect2, useLayoutEffect, useRef as useRef2 } from "react";
|
|
130
144
|
function OtpInputInput({ render, ...rest }) {
|
|
131
145
|
const { value, length, disabled, validationType, setSelection, commit, inputRef, slotAt } = useOtpInputRootContext("Input");
|
|
132
146
|
function select(input, index) {
|
|
@@ -139,6 +153,48 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
139
153
|
function report(input) {
|
|
140
154
|
setSelection({ start: input.selectionStart ?? 0, end: input.selectionEnd ?? 0 });
|
|
141
155
|
}
|
|
156
|
+
const pendingCaret = useRef2(null);
|
|
157
|
+
useLayoutEffect(() => {
|
|
158
|
+
const input = inputRef.current;
|
|
159
|
+
const at = pendingCaret.current;
|
|
160
|
+
pendingCaret.current = null;
|
|
161
|
+
if (input && at !== null && input.ownerDocument.activeElement === input) select(input, at);
|
|
162
|
+
});
|
|
163
|
+
function edit(next, caret) {
|
|
164
|
+
pendingCaret.current = caret;
|
|
165
|
+
commit(next);
|
|
166
|
+
}
|
|
167
|
+
const cleared = (index) => value.slice(0, index) + GAP + value.slice(index + 1);
|
|
168
|
+
function onDelete(event, input) {
|
|
169
|
+
const start = input.selectionStart ?? 0;
|
|
170
|
+
const end = input.selectionEnd ?? start;
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
if (end > start + 1) {
|
|
173
|
+
edit(value.slice(0, start) + GAP.repeat(end - start) + value.slice(end), start);
|
|
174
|
+
} else if (end === start + 1 && value.charAt(start) !== GAP) {
|
|
175
|
+
edit(cleared(start), start);
|
|
176
|
+
} else if (event.inputType.includes("Backward") && start > 0) {
|
|
177
|
+
edit(cleared(start - 1), start - 1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const beforeInput = useRef2(() => {
|
|
181
|
+
});
|
|
182
|
+
beforeInput.current = (event) => {
|
|
183
|
+
const input = event.currentTarget;
|
|
184
|
+
if (event.inputType.startsWith("delete") && event.inputType !== "deleteByCut") {
|
|
185
|
+
onDelete(event, input);
|
|
186
|
+
} else if (event.inputType === "insertText") {
|
|
187
|
+
const typed = event.data ?? "";
|
|
188
|
+
if (typed.length > 0 && sanitize(typed, validationType) !== typed) event.preventDefault();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
useEffect2(() => {
|
|
192
|
+
const input = inputRef.current;
|
|
193
|
+
if (!input) return;
|
|
194
|
+
const listener = (event) => beforeInput.current(event);
|
|
195
|
+
input.addEventListener("beforeinput", listener);
|
|
196
|
+
return () => input.removeEventListener("beforeinput", listener);
|
|
197
|
+
}, [inputRef]);
|
|
142
198
|
function syncSelection(event) {
|
|
143
199
|
const input = event.currentTarget;
|
|
144
200
|
const start = input.selectionStart ?? 0;
|
|
@@ -161,12 +217,6 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
161
217
|
maxLength: length,
|
|
162
218
|
pattern: validationType === "numeric" ? `\\d{${length}}` : `[A-Za-z0-9]{${length}}`,
|
|
163
219
|
disabled: disabled || void 0,
|
|
164
|
-
onBeforeInput(event) {
|
|
165
|
-
const typed = event.data;
|
|
166
|
-
if (typed && typed.length === 1 && !sanitize(typed, validationType)) {
|
|
167
|
-
event.preventDefault();
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
220
|
onChange(event) {
|
|
171
221
|
commit(event.currentTarget.value);
|
|
172
222
|
syncSelection(event);
|
|
@@ -177,12 +227,16 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
177
227
|
if (!pasted) return;
|
|
178
228
|
const input = event.currentTarget;
|
|
179
229
|
if (pasted.length >= length) {
|
|
180
|
-
|
|
230
|
+
edit(pasted, length);
|
|
181
231
|
return;
|
|
182
232
|
}
|
|
183
233
|
const start = input.selectionStart ?? value.length;
|
|
184
234
|
const end = input.selectionEnd ?? start;
|
|
185
|
-
|
|
235
|
+
const reach = start + pasted.length;
|
|
236
|
+
edit(
|
|
237
|
+
value.slice(0, start) + pasted + GAP.repeat(Math.max(0, end - reach)) + value.slice(Math.max(end, reach)),
|
|
238
|
+
reach
|
|
239
|
+
);
|
|
186
240
|
},
|
|
187
241
|
onKeyDown(event) {
|
|
188
242
|
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
@@ -193,13 +247,13 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
193
247
|
select(input, event.key === "ArrowLeft" ? at - 1 : at + 1);
|
|
194
248
|
},
|
|
195
249
|
onClick(event) {
|
|
196
|
-
if (event.detail > 1) return;
|
|
197
250
|
const index = slotAt(event.clientX);
|
|
198
251
|
if (index !== null) select(event.currentTarget, index);
|
|
199
252
|
},
|
|
200
253
|
onSelect: syncSelection,
|
|
201
254
|
onFocus(event) {
|
|
202
|
-
|
|
255
|
+
const gap = value.indexOf(GAP);
|
|
256
|
+
select(event.currentTarget, gap === -1 ? value.length : gap);
|
|
203
257
|
},
|
|
204
258
|
onBlur() {
|
|
205
259
|
setSelection(null);
|
|
@@ -213,7 +267,7 @@ function OtpInputInput({ render, ...rest }) {
|
|
|
213
267
|
import { useCallback as useCallback2 } from "react";
|
|
214
268
|
function OtpInputSlot({ index, className, children, render, ...rest }) {
|
|
215
269
|
const { value, length, disabled, selection, registerSlot } = useOtpInputRootContext("Slot");
|
|
216
|
-
const char = value.charAt(index);
|
|
270
|
+
const char = value.charAt(index) === GAP ? "" : value.charAt(index);
|
|
217
271
|
let active = false;
|
|
218
272
|
if (selection) {
|
|
219
273
|
active = selection.start === selection.end ? index === Math.min(selection.start, length - 1) : index >= selection.start && index < selection.end;
|
|
@@ -32,19 +32,34 @@ __export(index_parts_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/radio-group/RadioGroupRoot.tsx
|
|
35
|
-
var
|
|
35
|
+
var import_react5 = require("react");
|
|
36
36
|
|
|
37
|
-
// src/core/
|
|
37
|
+
// src/core/useLatest.ts
|
|
38
38
|
var import_react = require("react");
|
|
39
|
+
function useLatest(value) {
|
|
40
|
+
const ref = (0, import_react.useRef)(value);
|
|
41
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
42
|
+
ref.current = value;
|
|
43
|
+
});
|
|
44
|
+
return ref;
|
|
45
|
+
}
|
|
46
|
+
function afterReset(event, work) {
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
if (!event.defaultPrevented) work();
|
|
49
|
+
}, 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/core/useControlled.ts
|
|
53
|
+
var import_react2 = require("react");
|
|
39
54
|
function useControlled({
|
|
40
55
|
controlled,
|
|
41
56
|
default: defaultValue,
|
|
42
57
|
name = "Component",
|
|
43
58
|
state = "value"
|
|
44
59
|
}) {
|
|
45
|
-
const { current: isControlled } = (0,
|
|
46
|
-
const [uncontrolled, setUncontrolled] = (0,
|
|
47
|
-
const defaultRef = (0,
|
|
60
|
+
const { current: isControlled } = (0, import_react2.useRef)(controlled !== void 0);
|
|
61
|
+
const [uncontrolled, setUncontrolled] = (0, import_react2.useState)(defaultValue);
|
|
62
|
+
const defaultRef = (0, import_react2.useRef)(defaultValue);
|
|
48
63
|
const value = isControlled ? controlled : uncontrolled;
|
|
49
64
|
if (process.env.NODE_ENV !== "production") {
|
|
50
65
|
if (isControlled !== (controlled !== void 0)) {
|
|
@@ -58,7 +73,7 @@ function useControlled({
|
|
|
58
73
|
);
|
|
59
74
|
}
|
|
60
75
|
}
|
|
61
|
-
const setValue = (0,
|
|
76
|
+
const setValue = (0, import_react2.useCallback)(
|
|
62
77
|
(next) => {
|
|
63
78
|
if (!isControlled) setUncontrolled(next);
|
|
64
79
|
},
|
|
@@ -68,7 +83,7 @@ function useControlled({
|
|
|
68
83
|
}
|
|
69
84
|
|
|
70
85
|
// src/core/useRender.ts
|
|
71
|
-
var
|
|
86
|
+
var import_react3 = require("react");
|
|
72
87
|
|
|
73
88
|
// src/core/mergeProps.ts
|
|
74
89
|
function isEventHandler(key) {
|
|
@@ -150,17 +165,17 @@ function useRender({
|
|
|
150
165
|
consumerProps
|
|
151
166
|
}) {
|
|
152
167
|
const merged = mergeProps(props, consumerProps);
|
|
153
|
-
if ((0,
|
|
154
|
-
return (0,
|
|
168
|
+
if ((0, import_react3.isValidElement)(render)) {
|
|
169
|
+
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
155
170
|
}
|
|
156
|
-
return (0,
|
|
171
|
+
return (0, import_react3.createElement)(defaultTagName, merged);
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
// src/radio-group/RadioGroupRootContext.ts
|
|
160
|
-
var
|
|
161
|
-
var RadioGroupRootContext = (0,
|
|
175
|
+
var import_react4 = require("react");
|
|
176
|
+
var RadioGroupRootContext = (0, import_react4.createContext)(null);
|
|
162
177
|
function useRadioGroupRootContext() {
|
|
163
|
-
const context = (0,
|
|
178
|
+
const context = (0, import_react4.useContext)(RadioGroupRootContext);
|
|
164
179
|
if (context === null) {
|
|
165
180
|
throw new Error("<RadioGroup.Item> must be rendered inside <RadioGroup.Root>.");
|
|
166
181
|
}
|
|
@@ -208,34 +223,35 @@ function RadioGroupRoot({
|
|
|
208
223
|
name: "RadioGroup.Root",
|
|
209
224
|
state: "value"
|
|
210
225
|
});
|
|
211
|
-
const generatedName = (0,
|
|
226
|
+
const generatedName = (0, import_react5.useId)();
|
|
212
227
|
const name = nameProp ?? generatedName;
|
|
213
|
-
const commitValue = (0,
|
|
228
|
+
const commitValue = (0, import_react5.useCallback)(
|
|
214
229
|
(next) => {
|
|
215
230
|
setValue(next);
|
|
216
231
|
onValueChange?.(next);
|
|
217
232
|
},
|
|
218
233
|
[setValue, onValueChange]
|
|
219
234
|
);
|
|
220
|
-
const elementRef = (0,
|
|
221
|
-
const { current: initialValue } = (0,
|
|
222
|
-
(
|
|
235
|
+
const elementRef = (0, import_react5.useRef)(null);
|
|
236
|
+
const { current: initialValue } = (0, import_react5.useRef)(value);
|
|
237
|
+
const latest = useLatest({ value, commit: commitValue });
|
|
238
|
+
(0, import_react5.useEffect)(() => {
|
|
223
239
|
const owner = radiosIn(elementRef.current, name)[0]?.form;
|
|
224
240
|
if (!owner) return;
|
|
225
241
|
function onReset(event) {
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
afterReset(event, () => {
|
|
243
|
+
const { value: current, commit } = latest.current;
|
|
228
244
|
for (const input of radiosIn(elementRef.current, name)) {
|
|
229
|
-
input.checked = input.value ===
|
|
245
|
+
input.checked = input.value === current;
|
|
230
246
|
}
|
|
231
|
-
if (
|
|
232
|
-
|
|
247
|
+
if (current === initialValue) return;
|
|
248
|
+
commit(initialValue);
|
|
233
249
|
});
|
|
234
250
|
}
|
|
235
251
|
owner.addEventListener("reset", onReset);
|
|
236
252
|
return () => owner.removeEventListener("reset", onReset);
|
|
237
|
-
}, [name,
|
|
238
|
-
const state = (0,
|
|
253
|
+
}, [name, latest, initialValue]);
|
|
254
|
+
const state = (0, import_react5.useMemo)(
|
|
239
255
|
() => ({ value, disabled, required, name, form, select: commitValue }),
|
|
240
256
|
[value, disabled, required, name, form, commitValue]
|
|
241
257
|
);
|
|
@@ -257,7 +273,7 @@ function RadioGroupRoot({
|
|
|
257
273
|
}
|
|
258
274
|
|
|
259
275
|
// src/radio-group/RadioGroupItem.tsx
|
|
260
|
-
var
|
|
276
|
+
var import_react6 = require("react");
|
|
261
277
|
function RadioGroupItem({
|
|
262
278
|
value,
|
|
263
279
|
disabled: disabledProp = false,
|
|
@@ -268,7 +284,7 @@ function RadioGroupItem({
|
|
|
268
284
|
const group = useRadioGroupRootContext();
|
|
269
285
|
const disabled = group.disabled || disabledProp;
|
|
270
286
|
const checked = group.value === value;
|
|
271
|
-
const state = (0,
|
|
287
|
+
const state = (0, import_react6.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
272
288
|
return useRender({
|
|
273
289
|
render,
|
|
274
290
|
defaultTagName: "input",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterReset,
|
|
3
|
+
useLatest
|
|
4
|
+
} from "../chunk-JJ5FACZU.js";
|
|
1
5
|
import {
|
|
2
6
|
useControlled
|
|
3
7
|
} from "../chunk-UEIB3I3T.js";
|
|
@@ -79,22 +83,23 @@ function RadioGroupRoot({
|
|
|
79
83
|
);
|
|
80
84
|
const elementRef = useRef(null);
|
|
81
85
|
const { current: initialValue } = useRef(value);
|
|
86
|
+
const latest = useLatest({ value, commit: commitValue });
|
|
82
87
|
useEffect(() => {
|
|
83
88
|
const owner = radiosIn(elementRef.current, name)[0]?.form;
|
|
84
89
|
if (!owner) return;
|
|
85
90
|
function onReset(event) {
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
afterReset(event, () => {
|
|
92
|
+
const { value: current, commit } = latest.current;
|
|
88
93
|
for (const input of radiosIn(elementRef.current, name)) {
|
|
89
|
-
input.checked = input.value ===
|
|
94
|
+
input.checked = input.value === current;
|
|
90
95
|
}
|
|
91
|
-
if (
|
|
92
|
-
|
|
96
|
+
if (current === initialValue) return;
|
|
97
|
+
commit(initialValue);
|
|
93
98
|
});
|
|
94
99
|
}
|
|
95
100
|
owner.addEventListener("reset", onReset);
|
|
96
101
|
return () => owner.removeEventListener("reset", onReset);
|
|
97
|
-
}, [name,
|
|
102
|
+
}, [name, latest, initialValue]);
|
|
98
103
|
const state = useMemo(
|
|
99
104
|
() => ({ value, disabled, required, name, form, select: commitValue }),
|
|
100
105
|
[value, disabled, required, name, form, commitValue]
|
package/dist/switch/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ __export(index_parts_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/switch/SwitchRoot.tsx
|
|
35
|
-
var
|
|
35
|
+
var import_react6 = require("react");
|
|
36
36
|
|
|
37
37
|
// src/core/useControlled.ts
|
|
38
38
|
var import_react = require("react");
|
|
@@ -68,7 +68,24 @@ function useControlled({
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// src/core/useFormReset.ts
|
|
71
|
+
var import_react3 = require("react");
|
|
72
|
+
|
|
73
|
+
// src/core/useLatest.ts
|
|
71
74
|
var import_react2 = require("react");
|
|
75
|
+
function useLatest(value) {
|
|
76
|
+
const ref = (0, import_react2.useRef)(value);
|
|
77
|
+
(0, import_react2.useLayoutEffect)(() => {
|
|
78
|
+
ref.current = value;
|
|
79
|
+
});
|
|
80
|
+
return ref;
|
|
81
|
+
}
|
|
82
|
+
function afterReset(event, work) {
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
if (!event.defaultPrevented) work();
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/useFormReset.ts
|
|
72
89
|
function useFormReset({
|
|
73
90
|
elementRef,
|
|
74
91
|
inputRef,
|
|
@@ -76,25 +93,26 @@ function useFormReset({
|
|
|
76
93
|
inputChecked,
|
|
77
94
|
commit
|
|
78
95
|
}) {
|
|
79
|
-
const { current: initialValue } = (0,
|
|
80
|
-
(
|
|
96
|
+
const { current: initialValue } = (0, import_react3.useRef)(value);
|
|
97
|
+
const latest = useLatest({ value, inputChecked, commit });
|
|
98
|
+
(0, import_react3.useEffect)(() => {
|
|
81
99
|
const form = elementRef.current?.form;
|
|
82
100
|
if (!form) return;
|
|
83
101
|
function onReset(event) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (inputRef.current) inputRef.current.checked =
|
|
87
|
-
if (
|
|
88
|
-
|
|
102
|
+
afterReset(event, () => {
|
|
103
|
+
const { value: current, inputChecked: checked, commit: set } = latest.current;
|
|
104
|
+
if (inputRef.current) inputRef.current.checked = checked;
|
|
105
|
+
if (current === initialValue) return;
|
|
106
|
+
set(initialValue);
|
|
89
107
|
});
|
|
90
108
|
}
|
|
91
109
|
form.addEventListener("reset", onReset);
|
|
92
110
|
return () => form.removeEventListener("reset", onReset);
|
|
93
|
-
}, [elementRef, inputRef,
|
|
111
|
+
}, [elementRef, inputRef, latest, initialValue]);
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
// src/core/useRender.ts
|
|
97
|
-
var
|
|
115
|
+
var import_react4 = require("react");
|
|
98
116
|
|
|
99
117
|
// src/core/mergeProps.ts
|
|
100
118
|
function isEventHandler(key) {
|
|
@@ -176,17 +194,17 @@ function useRender({
|
|
|
176
194
|
consumerProps
|
|
177
195
|
}) {
|
|
178
196
|
const merged = mergeProps(props, consumerProps);
|
|
179
|
-
if ((0,
|
|
180
|
-
return (0,
|
|
197
|
+
if ((0, import_react4.isValidElement)(render)) {
|
|
198
|
+
return (0, import_react4.cloneElement)(render, mergeProps(merged, render.props));
|
|
181
199
|
}
|
|
182
|
-
return (0,
|
|
200
|
+
return (0, import_react4.createElement)(defaultTagName, merged);
|
|
183
201
|
}
|
|
184
202
|
|
|
185
203
|
// src/switch/SwitchRootContext.ts
|
|
186
|
-
var
|
|
187
|
-
var SwitchRootContext = (0,
|
|
204
|
+
var import_react5 = require("react");
|
|
205
|
+
var SwitchRootContext = (0, import_react5.createContext)(null);
|
|
188
206
|
function useSwitchRootContext() {
|
|
189
|
-
const context = (0,
|
|
207
|
+
const context = (0, import_react5.useContext)(SwitchRootContext);
|
|
190
208
|
if (context === null) {
|
|
191
209
|
throw new Error("<Switch.Thumb> must be rendered inside <Switch.Root>.");
|
|
192
210
|
}
|
|
@@ -222,16 +240,16 @@ function SwitchRoot({
|
|
|
222
240
|
name: "Switch.Root",
|
|
223
241
|
state: "checked"
|
|
224
242
|
});
|
|
225
|
-
const commitChecked = (0,
|
|
243
|
+
const commitChecked = (0, import_react6.useCallback)(
|
|
226
244
|
(next) => {
|
|
227
245
|
setChecked(next);
|
|
228
246
|
onCheckedChange?.(next);
|
|
229
247
|
},
|
|
230
248
|
[setChecked, onCheckedChange]
|
|
231
249
|
);
|
|
232
|
-
const state = (0,
|
|
233
|
-
const elementRef = (0,
|
|
234
|
-
const inputRef = (0,
|
|
250
|
+
const state = (0, import_react6.useMemo)(() => ({ checked, disabled }), [checked, disabled]);
|
|
251
|
+
const elementRef = (0, import_react6.useRef)(null);
|
|
252
|
+
const inputRef = (0, import_react6.useRef)(null);
|
|
235
253
|
useFormReset({
|
|
236
254
|
elementRef,
|
|
237
255
|
inputRef,
|
package/dist/switch/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.19.1",
|
|
4
4
|
"description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|