@corvu-next/otp-field 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2025 Jasmin Noetzli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ import * as _solidjs_web from '@solidjs/web';
2
+ import { ValidComponent, JSX } from '@solidjs/web';
3
+ import { Accessor } from 'solid-js';
4
+ import { Ref, ElementOf } from '@corvu-next/utils/dom';
5
+ import { DynamicProps } from '@corvu-next/utils/dynamic';
6
+ export { DynamicProps } from '@corvu-next/utils/dynamic';
7
+
8
+ type OtpFieldContextValue = {
9
+ /** The value of the OTP Field. */
10
+ value: Accessor<string>;
11
+ /** Whether the OTP Field is currently focused. */
12
+ isFocused: Accessor<boolean>;
13
+ /** Whether the OTP Field is currently hovered. */
14
+ isHovered: Accessor<boolean>;
15
+ /** Whether the user is currently inserting a value and a fake caret should be shown. */
16
+ isInserting: Accessor<boolean>;
17
+ /** The maximum number of chars in the OTP Field. */
18
+ maxLength: Accessor<number>;
19
+ /** The currently active slots in the OTP Field. */
20
+ activeSlots: Accessor<number[]>;
21
+ /** Whether to create extra space on the right for password managers. */
22
+ shiftPWManagers: Accessor<boolean>;
23
+ };
24
+ /** Context which exposes various properties to interact with the OTP Field. Optionally provide a contextId to access a keyed context. */
25
+ declare const useOtpFieldContext: (contextId?: string) => OtpFieldContextValue;
26
+
27
+ type OtpFieldInputCorvuProps = {
28
+ /**
29
+ * Regex pattern for the input. `null` disables the pattern and allow all chars.
30
+ * @defaultValue `'^\\d*$'`
31
+ */
32
+ pattern?: string | null;
33
+ /** Override the styles to apply when JavaScript is disabled. corvu provides a default for this but you're free to define your own styling. `null` disables the fallback. */
34
+ noScriptCSSFallback?: string | null;
35
+ /**
36
+ * The `id` of the OTP Field context. Useful if you have nested OTP Fields and want to create components that belong to an OTP Field higher up in the tree.
37
+ */
38
+ contextId?: string;
39
+ };
40
+ type OtpFieldInputSharedElementProps<T extends ValidComponent = 'input'> = {
41
+ ref: Ref<ElementOf<T>>;
42
+ onInput: JSX.EventHandlerUnion<ElementOf<T>, InputEvent>;
43
+ onFocus: JSX.EventHandlerUnion<ElementOf<T>, FocusEvent>;
44
+ onBlur: JSX.EventHandlerUnion<ElementOf<T>, FocusEvent>;
45
+ onMouseOver: JSX.EventHandlerUnion<ElementOf<T>, MouseEvent>;
46
+ onMouseLeave: JSX.EventHandlerUnion<ElementOf<T>, MouseEvent>;
47
+ onKeyDown: JSX.EventHandlerUnion<ElementOf<T>, KeyboardEvent>;
48
+ onKeyUp: JSX.EventHandlerUnion<ElementOf<T>, KeyboardEvent>;
49
+ inputMode: string;
50
+ autocomplete: string | undefined;
51
+ disabled: boolean | undefined;
52
+ spellcheck: boolean | undefined;
53
+ style: string | JSX.CSSProperties;
54
+ };
55
+ type OtpFieldInputElementProps = OtpFieldInputSharedElementProps & {
56
+ pattern: string | undefined;
57
+ 'data-corvu-otp-field-input': '' | null;
58
+ };
59
+ type OtpFieldInputProps<T extends ValidComponent = 'input'> = OtpFieldInputCorvuProps & Partial<OtpFieldInputSharedElementProps<T>>;
60
+ /** The hidden input element for the OTP Field.
61
+ *
62
+ * @data `data-corvu-otp-field-input` - Present on every OTP Field input element.
63
+ */
64
+ declare const OtpFieldInput: <T extends ValidComponent = "input">(props: DynamicProps<T, OtpFieldInputProps<T>>) => JSX.Element;
65
+
66
+ type OtpFieldRootCorvuProps = {
67
+ /** Max number of chars. Is required. */
68
+ maxLength: number;
69
+ /** The value of the OTP Field. */
70
+ value?: string;
71
+ /** Callback fired when the OTP Field value changes. */
72
+ onValueChange?: (value: string) => void;
73
+ /** Callback fired when the OTP Field is filled. */
74
+ onComplete?: (value: string) => void;
75
+ /**
76
+ * Whether to create extra space on the right for password managers.
77
+ * @defaultValue `true`
78
+ */
79
+ shiftPWManagers?: boolean;
80
+ /**
81
+ * The `id` of the OTP Field context. Useful if you have nested OTP Fields and want to create components that belong to an OTP Field higher up in the tree.
82
+ */
83
+ contextId?: string;
84
+ };
85
+ type OtpFieldRootSharedElementProps<T extends ValidComponent = 'div'> = {
86
+ ref: Ref<ElementOf<T>>;
87
+ style: string | JSX.CSSProperties;
88
+ children: JSX.Element | ((props: OtpFieldRootChildrenProps) => JSX.Element);
89
+ };
90
+ type OtpFieldRootElementProps = OtpFieldRootSharedElementProps & {
91
+ 'data-corvu-otp-field-root': '' | null;
92
+ };
93
+ type OtpFieldRootProps<T extends ValidComponent = 'div'> = OtpFieldRootCorvuProps & Partial<OtpFieldRootSharedElementProps<T>>;
94
+ /** Props that are passed to the Root component children callback. */
95
+ type OtpFieldRootChildrenProps = {
96
+ /** The value of the OTP Field. */
97
+ value: string;
98
+ /** Whether the OTP Field is currently focused. */
99
+ isFocused: boolean;
100
+ /** Whether the OTP Field is currently hovered. */
101
+ isHovered: boolean;
102
+ /** Whether the user is currently inserting a value and a fake caret should be shown. */
103
+ isInserting: boolean;
104
+ /** The maximum number of chars in the OTP Field. */
105
+ maxLength: number;
106
+ /** The currently active slots in the OTP Field. */
107
+ activeSlots: number[];
108
+ /** Whether to create extra space on the right for password managers. */
109
+ shiftPWManagers: boolean;
110
+ };
111
+ /** OTP Field root component. Is the wrapper to position the hidden OTP Field input element and provides the context.
112
+ *
113
+ * @data `data-corvu-otp-field-root` - Present on every OTP Field root element.
114
+ */
115
+ declare const OtpFieldRoot: <T extends ValidComponent = "div">(props: DynamicProps<T, OtpFieldRootProps<T>>) => JSX.Element;
116
+
117
+ declare const OtpField: (<T extends _solidjs_web.ValidComponent = "div">(props: DynamicProps<T, OtpFieldRootProps<T>>) => _solidjs_web.JSX.Element) & {
118
+ Input: <T extends _solidjs_web.ValidComponent = "input">(props: DynamicProps<T, OtpFieldInputProps<T>>) => _solidjs_web.JSX.Element;
119
+ useContext: (contextId?: string) => OtpFieldContextValue;
120
+ };
121
+
122
+ export { type OtpFieldContextValue as ContextValue, OtpFieldInput as Input, type OtpFieldInputCorvuProps as InputCorvuProps, type OtpFieldInputElementProps as InputElementProps, type OtpFieldInputProps as InputProps, type OtpFieldInputSharedElementProps as InputSharedElementProps, OtpFieldRoot as Root, type OtpFieldRootChildrenProps as RootChildrenProps, type OtpFieldRootCorvuProps as RootCorvuProps, type OtpFieldRootElementProps as RootElementProps, type OtpFieldRootProps as RootProps, type OtpFieldRootSharedElementProps as RootSharedElementProps, OtpField as default, useOtpFieldContext as useContext };
package/dist/index.js ADDED
@@ -0,0 +1,469 @@
1
+ import { createContext, merge, omit, createSignal, createEffect, createMemo, untrack, useContext, Show } from 'solid-js';
2
+ import { useKeyedContext, createKeyedContext } from '@corvu-next/utils/create/keyedContext';
3
+ import { createComponent, mergeProps, template } from 'solid-js/web';
4
+ import { combineStyle, afterPaint, callEventHandler } from '@corvu-next/utils/dom';
5
+ import { isServer } from '@solidjs/web';
6
+ import { Dynamic } from '@corvu-next/utils/dynamic';
7
+ import { mergeRefs } from '@corvu-next/utils/reactivity';
8
+ import createControllableSignal from '@corvu-next/utils/create/controllableSignal';
9
+ import createOnce from '@corvu-next/utils/create/once';
10
+ import createSize from '@corvu-next/utils/create/size';
11
+ import { isFunction } from '@corvu-next/utils';
12
+
13
+ // src/context.ts
14
+ var OtpFieldContext = createContext(null);
15
+ var createOtpFieldContext = (contextId) => {
16
+ if (contextId === void 0) return OtpFieldContext;
17
+ const context = createKeyedContext(
18
+ `otp-field-${contextId}`
19
+ );
20
+ return context;
21
+ };
22
+ var useOtpFieldContext = (contextId) => {
23
+ if (contextId === void 0) {
24
+ const context2 = useContext(OtpFieldContext);
25
+ if (!context2) {
26
+ throw new Error(
27
+ "[corvu]: OTP Field context not found. Make sure to wrap OTP Field components in <OtpField.Root>"
28
+ );
29
+ }
30
+ return context2;
31
+ }
32
+ const context = useKeyedContext(
33
+ `otp-field-${contextId}`
34
+ );
35
+ if (!context) {
36
+ throw new Error(
37
+ `[corvu]: OTP Field context with id "${contextId}" not found. Make sure to wrap OTP Field components in <OtpField.Root contextId="${contextId}">`
38
+ );
39
+ }
40
+ return context;
41
+ };
42
+ var InternalOtpFieldContext = createContext(null);
43
+ var createInternalOtpFieldContext = (contextId) => {
44
+ if (contextId === void 0) return InternalOtpFieldContext;
45
+ const context = createKeyedContext(
46
+ `otp-field-internal-${contextId}`
47
+ );
48
+ return context;
49
+ };
50
+ var useInternalOtpFieldContext = (contextId) => {
51
+ if (contextId === void 0) {
52
+ const context2 = useContext(InternalOtpFieldContext);
53
+ if (!context2) {
54
+ throw new Error(
55
+ "[corvu]: OTP Field context not found. Make sure to wrap OTP Field components in <OtpField.Root>"
56
+ );
57
+ }
58
+ return context2;
59
+ }
60
+ const context = useKeyedContext(
61
+ `otp-field-internal-${contextId}`
62
+ );
63
+ if (!context) {
64
+ throw new Error(
65
+ `[corvu]: OTP Field context with id "${contextId}" not found. Make sure to wrap OTP Field components in <OtpField.Root contextId="${contextId}">`
66
+ );
67
+ }
68
+ return context;
69
+ };
70
+ var otpFieldStyleElement = null;
71
+ var activeCount = 0;
72
+ var createOtpFieldStyleElement = () => {
73
+ activeCount += 1;
74
+ if (otpFieldStyleElement) return;
75
+ otpFieldStyleElement = document.createElement("style");
76
+ document.head.appendChild(otpFieldStyleElement);
77
+ const autofillStyle = "background: transparent !important; color: transparent !important; border-color: transparent !important; opacity: 0 !important; box-shadow: none !important; -webkit-box-shadow: none !important; -webkit-text-fill-color: transparent !important;";
78
+ const styleString = `
79
+ [data-corvu-otp-field-input]::selection { background: transparent !important; color: transparent !important; }';
80
+ [data-corvu-otp-field-input]:autofill { ${autofillStyle} };
81
+ [data-corvu-otp-field-input]:-webkit-autofill { ${autofillStyle} };
82
+ @supports (-webkit-touch-callout: none) { [data-corvu-otp-field-input] { letter-spacing: -.6em !important; font-weight: 100 !important; font-stretch: ultra-condensed; font-optical-sizing: none !important; left: -1px !important; right: 1px !important; } };
83
+ [data-corvu-otp-field-input] + * { pointer-events: all !important; };
84
+ `;
85
+ otpFieldStyleElement.innerHTML = styleString;
86
+ createEffect(() => {
87
+ return () => {
88
+ activeCount -= 1;
89
+ if (activeCount === 0 && otpFieldStyleElement) {
90
+ otpFieldStyleElement.remove();
91
+ otpFieldStyleElement = null;
92
+ }
93
+ };
94
+ });
95
+ };
96
+ var style_default = createOtpFieldStyleElement;
97
+ var _tmpl$ = /* @__PURE__ */ template(`<noscript>`);
98
+ var OtpFieldInput = (props) => {
99
+ const defaultedProps = merge({
100
+ pattern: "^\\d*$",
101
+ noScriptCSSFallback: DEFAULT_NOSCRIPT_CSS_FALLBACK
102
+ }, props);
103
+ const otherProps = omit(defaultedProps, "pattern", "noScriptCSSFallback", "ref", "onInput", "onFocus", "onBlur", "onMouseOver", "onMouseLeave", "onKeyDown", "onKeyUp", "autocomplete", "disabled", "spellcheck", "style", "contextId");
104
+ const previousSelection = {
105
+ inserting: false,
106
+ start: null,
107
+ end: null
108
+ };
109
+ let shiftKeyDown = false;
110
+ const [ref, setRef] = createSignal(null);
111
+ const context = createMemo(() => useInternalOtpFieldContext(defaultedProps.contextId));
112
+ createEffect(() => {
113
+ style_default();
114
+ const onSelectionChangeWrapper = () => onSelectionChange();
115
+ document.addEventListener("selectionchange", onSelectionChangeWrapper);
116
+ return () => {
117
+ document.removeEventListener("selectionchange", onSelectionChangeWrapper);
118
+ };
119
+ });
120
+ createEffect(() => {
121
+ const element = ref();
122
+ if (!element) return void 0;
123
+ const form = element.form;
124
+ if (!form) return void 0;
125
+ const onReset = () => {
126
+ afterPaint(() => {
127
+ context().setValue(element.value);
128
+ });
129
+ };
130
+ form.addEventListener("reset", onReset);
131
+ return () => {
132
+ form.removeEventListener("reset", onReset);
133
+ };
134
+ });
135
+ createEffect(() => {
136
+ const element = ref();
137
+ if (!element) return void 0;
138
+ element.value = context().value();
139
+ return void 0;
140
+ });
141
+ const patternRegex = createMemo(() => defaultedProps.pattern !== null ? new RegExp(defaultedProps.pattern) : void 0);
142
+ const onInput = (event) => {
143
+ if (callEventHandler(defaultedProps.onInput, event)) return;
144
+ const rawValue = event.currentTarget.value;
145
+ let finalValue = rawValue;
146
+ const contextValue = context().value();
147
+ const selectionSize = Math.abs((previousSelection.start ?? 0) - (previousSelection.end ?? 0));
148
+ const regex = patternRegex();
149
+ if ((previousSelection.inserting || selectionSize === contextValue.length) && regex) {
150
+ finalValue = finalValue.replace(new RegExp(`[^${regex.source}]`, "g"), "");
151
+ }
152
+ finalValue = finalValue.slice(0, context().maxLength());
153
+ const hasInvalidChars = !!regex && !regex.test(finalValue);
154
+ if (rawValue.length !== 0 && finalValue.length === 0 || finalValue === contextValue || hasInvalidChars) {
155
+ event.preventDefault();
156
+ event.currentTarget.value = contextValue;
157
+ if (hasInvalidChars) {
158
+ event.currentTarget.setSelectionRange(previousSelection.start ?? 0, previousSelection.end ?? 0);
159
+ }
160
+ return;
161
+ }
162
+ if (finalValue.length < contextValue.length) {
163
+ onSelectionChange(event.inputType);
164
+ }
165
+ context().setValue(finalValue);
166
+ };
167
+ const onFocus = (event) => {
168
+ if (callEventHandler(defaultedProps.onFocus, event)) return;
169
+ event.currentTarget.setSelectionRange(context().value().length, context().value().length);
170
+ context().setIsFocused(true);
171
+ onSelectionChange();
172
+ };
173
+ const onBlur = (event) => {
174
+ if (callEventHandler(defaultedProps.onBlur, event)) return;
175
+ shiftKeyDown = false;
176
+ context().setIsFocused(false);
177
+ onSelectionChange();
178
+ };
179
+ const onMouseOver = (event) => {
180
+ !callEventHandler(defaultedProps.onMouseOver, event) && defaultedProps.disabled !== true && context().setIsHovered(true);
181
+ };
182
+ const onMouseLeave = (event) => {
183
+ !callEventHandler(defaultedProps.onMouseLeave, event) && context().setIsHovered(false);
184
+ };
185
+ const onKeyDown = (event) => {
186
+ if (callEventHandler(defaultedProps.onKeyDown, event)) return;
187
+ if (event.key !== "Shift") return;
188
+ shiftKeyDown = true;
189
+ };
190
+ const onKeyUp = (event) => {
191
+ if (callEventHandler(defaultedProps.onKeyUp, event)) return;
192
+ if (event.key !== "Shift") return;
193
+ shiftKeyDown = false;
194
+ };
195
+ const onSelectionChange = (inputType) => {
196
+ const element = ref();
197
+ if (!element) return;
198
+ if (context().isFocused() === false || document.activeElement !== element || element.selectionStart === null || element.selectionEnd === null) {
199
+ syncSelection({
200
+ start: null,
201
+ end: null,
202
+ inserting: false,
203
+ originalStart: element.selectionStart,
204
+ originalEnd: element.selectionEnd
205
+ });
206
+ context().setIsInserting(false);
207
+ return;
208
+ }
209
+ const maxLength = context().maxLength();
210
+ const inserting = element.value.length < maxLength && element.selectionStart === element.value.length;
211
+ context().setIsInserting(inserting);
212
+ if (inserting || element.selectionStart !== element.selectionEnd) {
213
+ syncSelection({
214
+ start: element.selectionStart,
215
+ end: inserting ? element.selectionEnd + 1 : element.selectionEnd,
216
+ inserting,
217
+ originalStart: element.selectionStart,
218
+ originalEnd: element.selectionEnd
219
+ });
220
+ return;
221
+ }
222
+ let selectionStart = 0;
223
+ let selectionEnd = 0;
224
+ let direction = void 0;
225
+ if (element.selectionStart === 0) {
226
+ selectionStart = 0;
227
+ selectionEnd = 1;
228
+ direction = "forward";
229
+ } else if (element.selectionStart === maxLength) {
230
+ selectionStart = maxLength - 1;
231
+ selectionEnd = maxLength;
232
+ direction = "backward";
233
+ } else {
234
+ let startOffset = 0;
235
+ let endOffset = 1;
236
+ if (previousSelection.start !== null && previousSelection.end !== null) {
237
+ const navigatedBackwards = element.selectionStart < previousSelection.end && Math.abs(previousSelection.start - previousSelection.end) === 1;
238
+ direction = navigatedBackwards ? "backward" : "forward";
239
+ if (navigatedBackwards && !previousSelection.inserting && inputType !== "deleteContentForward" || !navigatedBackwards && shiftKeyDown) {
240
+ startOffset += -1;
241
+ }
242
+ }
243
+ if (shiftKeyDown && inputType === void 0) {
244
+ endOffset += 1;
245
+ }
246
+ selectionStart = element.selectionStart + startOffset;
247
+ selectionEnd = element.selectionEnd + startOffset + endOffset;
248
+ }
249
+ element.setSelectionRange(selectionStart, selectionEnd, direction);
250
+ syncSelection({
251
+ start: selectionStart,
252
+ end: selectionEnd,
253
+ inserting,
254
+ originalStart: element.selectionStart,
255
+ originalEnd: element.selectionEnd
256
+ });
257
+ };
258
+ const syncSelection = (props2) => {
259
+ previousSelection.inserting = props2.inserting;
260
+ previousSelection.start = props2.originalStart;
261
+ previousSelection.end = props2.originalEnd;
262
+ const start = props2.start;
263
+ const end = props2.end;
264
+ if (start === null || end === null) {
265
+ context().setActiveSlots([]);
266
+ return;
267
+ }
268
+ const indexes = Array.from({
269
+ length: end - start
270
+ }, (_, i) => start + i);
271
+ context().setActiveSlots(indexes);
272
+ };
273
+ return [createComponent(Show, {
274
+ get when() {
275
+ return defaultedProps.noScriptCSSFallback !== null && isServer;
276
+ },
277
+ get children() {
278
+ return _tmpl$();
279
+ }
280
+ }), createComponent(Dynamic, mergeProps({
281
+ as: "input",
282
+ ref(r$) {
283
+ var _ref$ = mergeRefs(setRef, defaultedProps.ref);
284
+ typeof _ref$ === "function" && _ref$(r$);
285
+ },
286
+ onInput,
287
+ onFocus,
288
+ onBlur,
289
+ onMouseOver,
290
+ onMouseLeave,
291
+ onKeyDown,
292
+ onKeyUp,
293
+ inputMode: "numeric",
294
+ get autocomplete() {
295
+ return defaultedProps.autocomplete ?? "one-time-code";
296
+ },
297
+ get disabled() {
298
+ return defaultedProps.disabled;
299
+ },
300
+ get spellcheck() {
301
+ return defaultedProps.spellcheck ?? false;
302
+ },
303
+ get style() {
304
+ return combineStyle({
305
+ display: "flex",
306
+ position: "absolute",
307
+ inset: 0,
308
+ width: context().shiftPWManagers() ? "calc(100% + 40px)" : "100%",
309
+ "clip-path": context().shiftPWManagers() ? "inset(0 40px 0 0)" : void 0,
310
+ height: "100%",
311
+ padding: 0,
312
+ color: "transparent",
313
+ background: "transparent",
314
+ "caret-color": "transparent",
315
+ border: "0 solid transparent",
316
+ outline: "0 solid transparent",
317
+ "box-shadow": "none",
318
+ "line-height": "1",
319
+ "letter-spacing": "-1em",
320
+ "font-family": "monospace",
321
+ "font-variant-numeric": "tabular-nums",
322
+ "font-size": `${context().rootHeight()}px`,
323
+ "pointer-events": "all"
324
+ }, defaultedProps.style);
325
+ },
326
+ get pattern() {
327
+ return patternRegex()?.source;
328
+ },
329
+ "data-corvu-otp-field-input": ""
330
+ }, otherProps))];
331
+ };
332
+ var DEFAULT_NOSCRIPT_CSS_FALLBACK = `
333
+ [data-corvu-otp-field-input] {
334
+ color: black !important;
335
+ background-color: white !important;
336
+ caret-color: black !important;
337
+ letter-spacing: inherit !important;
338
+ text-align: center !important;
339
+ border: 1px solid black !important;
340
+ width: 100% !important;
341
+ font-size: inherit !important;
342
+ clip-path: none !important;
343
+ }
344
+ `;
345
+ var Input_default = OtpFieldInput;
346
+ var OtpFieldRoot = (props) => {
347
+ const defaultedProps = merge({
348
+ shiftPWManagers: true
349
+ }, props);
350
+ const localProps = omit(defaultedProps, "maxLength", "value", "onValueChange", "onComplete", "shiftPWManagers", "contextId", "ref", "style", "children");
351
+ const [ref, setRef] = createSignal(null);
352
+ const [value, setValue] = createControllableSignal({
353
+ value: () => defaultedProps.value,
354
+ initialValue: "",
355
+ onChange: defaultedProps.onValueChange
356
+ });
357
+ const rootHeight = createSize({
358
+ element: ref,
359
+ dimension: "height"
360
+ });
361
+ createEffect((prev) => {
362
+ const value_ = value();
363
+ if (value_.length !== defaultedProps.maxLength) return value_;
364
+ defaultedProps.onComplete?.(value_);
365
+ return value_;
366
+ });
367
+ const [isFocused, setIsFocused] = createSignal(false);
368
+ const [isHovered, setIsHovered] = createSignal(false);
369
+ const [isInserting, setIsInserting] = createSignal(false);
370
+ const [activeSlots, setActiveSlots] = createSignal([]);
371
+ const childrenProps = {
372
+ get value() {
373
+ return value();
374
+ },
375
+ get isFocused() {
376
+ return isFocused();
377
+ },
378
+ get isHovered() {
379
+ return isHovered();
380
+ },
381
+ get isInserting() {
382
+ return isInserting();
383
+ },
384
+ get maxLength() {
385
+ return defaultedProps.maxLength;
386
+ },
387
+ get activeSlots() {
388
+ return activeSlots();
389
+ },
390
+ get shiftPWManagers() {
391
+ return defaultedProps.shiftPWManagers;
392
+ }
393
+ };
394
+ const memoizedChildren = createOnce(() => defaultedProps.children);
395
+ const resolveChildren = () => {
396
+ const children = memoizedChildren()();
397
+ if (isFunction(children)) {
398
+ return children(childrenProps);
399
+ }
400
+ return children;
401
+ };
402
+ const memoizedOtpFieldRoot = createMemo(() => {
403
+ const OtpFieldContext2 = createOtpFieldContext(defaultedProps.contextId);
404
+ const InternalOtpFieldContext2 = createInternalOtpFieldContext(defaultedProps.contextId);
405
+ return createComponent(OtpFieldContext2, {
406
+ value: {
407
+ value,
408
+ isFocused,
409
+ isHovered,
410
+ isInserting,
411
+ maxLength: () => defaultedProps.maxLength,
412
+ activeSlots,
413
+ shiftPWManagers: () => defaultedProps.shiftPWManagers
414
+ },
415
+ get children() {
416
+ return createComponent(InternalOtpFieldContext2, {
417
+ value: {
418
+ value,
419
+ isFocused,
420
+ isHovered,
421
+ isInserting,
422
+ maxLength: () => defaultedProps.maxLength,
423
+ activeSlots,
424
+ shiftPWManagers: () => defaultedProps.shiftPWManagers,
425
+ rootHeight,
426
+ setValue,
427
+ setIsFocused,
428
+ setIsHovered,
429
+ setIsInserting,
430
+ setActiveSlots
431
+ },
432
+ get children() {
433
+ return createComponent(Dynamic, mergeProps({
434
+ as: "div",
435
+ ref(r$) {
436
+ var _ref$ = mergeRefs(setRef, defaultedProps.ref);
437
+ typeof _ref$ === "function" && _ref$(r$);
438
+ },
439
+ get style() {
440
+ return combineStyle({
441
+ position: "relative",
442
+ "user-select": "none",
443
+ "-webkit-user-select": "none",
444
+ "pointer-events": "none"
445
+ }, defaultedProps.style);
446
+ },
447
+ "data-corvu-otp-field-root": ""
448
+ }, localProps, {
449
+ get children() {
450
+ return untrack(() => resolveChildren());
451
+ }
452
+ }));
453
+ }
454
+ });
455
+ }
456
+ });
457
+ });
458
+ return memoizedOtpFieldRoot;
459
+ };
460
+ var Root_default = OtpFieldRoot;
461
+
462
+ // src/index.ts
463
+ var OtpField = Object.assign(Root_default, {
464
+ Input: Input_default,
465
+ useContext: useOtpFieldContext
466
+ });
467
+ var index_default = OtpField;
468
+
469
+ export { Input_default as Input, Root_default as Root, index_default as default, useOtpFieldContext as useContext };
package/dist/index.jsx ADDED
@@ -0,0 +1,537 @@
1
+ // src/context.ts
2
+ import { createContext, useContext } from "solid-js";
3
+ import {
4
+ createKeyedContext,
5
+ useKeyedContext
6
+ } from "@corvu-next/utils/create/keyedContext";
7
+ var OtpFieldContext = createContext(null);
8
+ var createOtpFieldContext = (contextId) => {
9
+ if (contextId === void 0) return OtpFieldContext;
10
+ const context = createKeyedContext(
11
+ `otp-field-${contextId}`
12
+ );
13
+ return context;
14
+ };
15
+ var useOtpFieldContext = (contextId) => {
16
+ if (contextId === void 0) {
17
+ const context2 = useContext(OtpFieldContext);
18
+ if (!context2) {
19
+ throw new Error(
20
+ "[corvu]: OTP Field context not found. Make sure to wrap OTP Field components in <OtpField.Root>"
21
+ );
22
+ }
23
+ return context2;
24
+ }
25
+ const context = useKeyedContext(
26
+ `otp-field-${contextId}`
27
+ );
28
+ if (!context) {
29
+ throw new Error(
30
+ `[corvu]: OTP Field context with id "${contextId}" not found. Make sure to wrap OTP Field components in <OtpField.Root contextId="${contextId}">`
31
+ );
32
+ }
33
+ return context;
34
+ };
35
+ var InternalOtpFieldContext = createContext(null);
36
+ var createInternalOtpFieldContext = (contextId) => {
37
+ if (contextId === void 0) return InternalOtpFieldContext;
38
+ const context = createKeyedContext(
39
+ `otp-field-internal-${contextId}`
40
+ );
41
+ return context;
42
+ };
43
+ var useInternalOtpFieldContext = (contextId) => {
44
+ if (contextId === void 0) {
45
+ const context2 = useContext(InternalOtpFieldContext);
46
+ if (!context2) {
47
+ throw new Error(
48
+ "[corvu]: OTP Field context not found. Make sure to wrap OTP Field components in <OtpField.Root>"
49
+ );
50
+ }
51
+ return context2;
52
+ }
53
+ const context = useKeyedContext(
54
+ `otp-field-internal-${contextId}`
55
+ );
56
+ if (!context) {
57
+ throw new Error(
58
+ `[corvu]: OTP Field context with id "${contextId}" not found. Make sure to wrap OTP Field components in <OtpField.Root contextId="${contextId}">`
59
+ );
60
+ }
61
+ return context;
62
+ };
63
+
64
+ // src/Input.tsx
65
+ import {
66
+ afterPaint,
67
+ callEventHandler,
68
+ combineStyle
69
+ } from "@corvu-next/utils/dom";
70
+ import {
71
+ createEffect as createEffect2,
72
+ createMemo,
73
+ createSignal,
74
+ merge,
75
+ omit,
76
+ Show
77
+ } from "solid-js";
78
+ import { isServer } from "@solidjs/web";
79
+ import { Dynamic } from "@corvu-next/utils/dynamic";
80
+
81
+ // src/lib/style.ts
82
+ import { createEffect } from "solid-js";
83
+ var otpFieldStyleElement = null;
84
+ var activeCount = 0;
85
+ var createOtpFieldStyleElement = () => {
86
+ activeCount += 1;
87
+ if (otpFieldStyleElement) return;
88
+ otpFieldStyleElement = document.createElement("style");
89
+ document.head.appendChild(otpFieldStyleElement);
90
+ const autofillStyle = "background: transparent !important; color: transparent !important; border-color: transparent !important; opacity: 0 !important; box-shadow: none !important; -webkit-box-shadow: none !important; -webkit-text-fill-color: transparent !important;";
91
+ const styleString = `
92
+ [data-corvu-otp-field-input]::selection { background: transparent !important; color: transparent !important; }';
93
+ [data-corvu-otp-field-input]:autofill { ${autofillStyle} };
94
+ [data-corvu-otp-field-input]:-webkit-autofill { ${autofillStyle} };
95
+ @supports (-webkit-touch-callout: none) { [data-corvu-otp-field-input] { letter-spacing: -.6em !important; font-weight: 100 !important; font-stretch: ultra-condensed; font-optical-sizing: none !important; left: -1px !important; right: 1px !important; } };
96
+ [data-corvu-otp-field-input] + * { pointer-events: all !important; };
97
+ `;
98
+ otpFieldStyleElement.innerHTML = styleString;
99
+ createEffect(() => {
100
+ return () => {
101
+ activeCount -= 1;
102
+ if (activeCount === 0 && otpFieldStyleElement) {
103
+ otpFieldStyleElement.remove();
104
+ otpFieldStyleElement = null;
105
+ }
106
+ };
107
+ });
108
+ };
109
+ var style_default = createOtpFieldStyleElement;
110
+
111
+ // src/Input.tsx
112
+ import { mergeRefs } from "@corvu-next/utils/reactivity";
113
+ var OtpFieldInput = (props) => {
114
+ const defaultedProps = merge(
115
+ {
116
+ pattern: "^\\d*$",
117
+ noScriptCSSFallback: DEFAULT_NOSCRIPT_CSS_FALLBACK
118
+ },
119
+ props
120
+ );
121
+ const otherProps = omit(
122
+ defaultedProps,
123
+ "pattern",
124
+ "noScriptCSSFallback",
125
+ "ref",
126
+ "onInput",
127
+ "onFocus",
128
+ "onBlur",
129
+ "onMouseOver",
130
+ "onMouseLeave",
131
+ "onKeyDown",
132
+ "onKeyUp",
133
+ "autocomplete",
134
+ "disabled",
135
+ "spellcheck",
136
+ "style",
137
+ "contextId"
138
+ );
139
+ const previousSelection = {
140
+ inserting: false,
141
+ start: null,
142
+ end: null
143
+ };
144
+ let shiftKeyDown = false;
145
+ const [ref, setRef] = createSignal(null);
146
+ const context = createMemo(
147
+ () => useInternalOtpFieldContext(defaultedProps.contextId)
148
+ );
149
+ createEffect2(() => {
150
+ style_default();
151
+ const onSelectionChangeWrapper = () => onSelectionChange();
152
+ document.addEventListener("selectionchange", onSelectionChangeWrapper);
153
+ return () => {
154
+ document.removeEventListener("selectionchange", onSelectionChangeWrapper);
155
+ };
156
+ });
157
+ createEffect2(() => {
158
+ const element = ref();
159
+ if (!element) return void 0;
160
+ const form = element.form;
161
+ if (!form) return void 0;
162
+ const onReset = () => {
163
+ afterPaint(() => {
164
+ context().setValue(element.value);
165
+ });
166
+ };
167
+ form.addEventListener("reset", onReset);
168
+ return () => {
169
+ form.removeEventListener("reset", onReset);
170
+ };
171
+ });
172
+ createEffect2(() => {
173
+ const element = ref();
174
+ if (!element) return void 0;
175
+ element.value = context().value();
176
+ return void 0;
177
+ });
178
+ const patternRegex = createMemo(
179
+ () => defaultedProps.pattern !== null ? new RegExp(defaultedProps.pattern) : void 0
180
+ );
181
+ const onInput = (event) => {
182
+ if (callEventHandler(defaultedProps.onInput, event)) return;
183
+ const rawValue = event.currentTarget.value;
184
+ let finalValue = rawValue;
185
+ const contextValue = context().value();
186
+ const selectionSize = Math.abs(
187
+ (previousSelection.start ?? 0) - (previousSelection.end ?? 0)
188
+ );
189
+ const regex = patternRegex();
190
+ if ((previousSelection.inserting || selectionSize === contextValue.length) && regex) {
191
+ finalValue = finalValue.replace(new RegExp(`[^${regex.source}]`, "g"), "");
192
+ }
193
+ finalValue = finalValue.slice(0, context().maxLength());
194
+ const hasInvalidChars = !!regex && !regex.test(finalValue);
195
+ if (rawValue.length !== 0 && finalValue.length === 0 || finalValue === contextValue || hasInvalidChars) {
196
+ event.preventDefault();
197
+ event.currentTarget.value = contextValue;
198
+ if (hasInvalidChars) {
199
+ event.currentTarget.setSelectionRange(
200
+ previousSelection.start ?? 0,
201
+ previousSelection.end ?? 0
202
+ );
203
+ }
204
+ return;
205
+ }
206
+ if (finalValue.length < contextValue.length) {
207
+ onSelectionChange(event.inputType);
208
+ }
209
+ context().setValue(finalValue);
210
+ };
211
+ const onFocus = (event) => {
212
+ if (callEventHandler(defaultedProps.onFocus, event)) return;
213
+ event.currentTarget.setSelectionRange(
214
+ context().value().length,
215
+ context().value().length
216
+ );
217
+ context().setIsFocused(true);
218
+ onSelectionChange();
219
+ };
220
+ const onBlur = (event) => {
221
+ if (callEventHandler(defaultedProps.onBlur, event)) return;
222
+ shiftKeyDown = false;
223
+ context().setIsFocused(false);
224
+ onSelectionChange();
225
+ };
226
+ const onMouseOver = (event) => {
227
+ !callEventHandler(defaultedProps.onMouseOver, event) && defaultedProps.disabled !== true && context().setIsHovered(true);
228
+ };
229
+ const onMouseLeave = (event) => {
230
+ !callEventHandler(defaultedProps.onMouseLeave, event) && context().setIsHovered(false);
231
+ };
232
+ const onKeyDown = (event) => {
233
+ if (callEventHandler(defaultedProps.onKeyDown, event)) return;
234
+ if (event.key !== "Shift") return;
235
+ shiftKeyDown = true;
236
+ };
237
+ const onKeyUp = (event) => {
238
+ if (callEventHandler(defaultedProps.onKeyUp, event)) return;
239
+ if (event.key !== "Shift") return;
240
+ shiftKeyDown = false;
241
+ };
242
+ const onSelectionChange = (inputType) => {
243
+ const element = ref();
244
+ if (!element) return;
245
+ if (context().isFocused() === false || document.activeElement !== element || element.selectionStart === null || element.selectionEnd === null) {
246
+ syncSelection({
247
+ start: null,
248
+ end: null,
249
+ inserting: false,
250
+ originalStart: element.selectionStart,
251
+ originalEnd: element.selectionEnd
252
+ });
253
+ context().setIsInserting(false);
254
+ return;
255
+ }
256
+ const maxLength = context().maxLength();
257
+ const inserting = element.value.length < maxLength && element.selectionStart === element.value.length;
258
+ context().setIsInserting(inserting);
259
+ if (inserting || element.selectionStart !== element.selectionEnd) {
260
+ syncSelection({
261
+ start: element.selectionStart,
262
+ end: inserting ? element.selectionEnd + 1 : element.selectionEnd,
263
+ inserting,
264
+ originalStart: element.selectionStart,
265
+ originalEnd: element.selectionEnd
266
+ });
267
+ return;
268
+ }
269
+ let selectionStart = 0;
270
+ let selectionEnd = 0;
271
+ let direction = void 0;
272
+ if (element.selectionStart === 0) {
273
+ selectionStart = 0;
274
+ selectionEnd = 1;
275
+ direction = "forward";
276
+ } else if (element.selectionStart === maxLength) {
277
+ selectionStart = maxLength - 1;
278
+ selectionEnd = maxLength;
279
+ direction = "backward";
280
+ } else {
281
+ let startOffset = 0;
282
+ let endOffset = 1;
283
+ if (previousSelection.start !== null && previousSelection.end !== null) {
284
+ const navigatedBackwards = element.selectionStart < previousSelection.end && Math.abs(previousSelection.start - previousSelection.end) === 1;
285
+ direction = navigatedBackwards ? "backward" : "forward";
286
+ if (navigatedBackwards && !previousSelection.inserting && inputType !== "deleteContentForward" || !navigatedBackwards && shiftKeyDown) {
287
+ startOffset += -1;
288
+ }
289
+ }
290
+ if (shiftKeyDown && inputType === void 0) {
291
+ endOffset += 1;
292
+ }
293
+ selectionStart = element.selectionStart + startOffset;
294
+ selectionEnd = element.selectionEnd + startOffset + endOffset;
295
+ }
296
+ element.setSelectionRange(selectionStart, selectionEnd, direction);
297
+ syncSelection({
298
+ start: selectionStart,
299
+ end: selectionEnd,
300
+ inserting,
301
+ originalStart: element.selectionStart,
302
+ originalEnd: element.selectionEnd
303
+ });
304
+ };
305
+ const syncSelection = (props2) => {
306
+ previousSelection.inserting = props2.inserting;
307
+ previousSelection.start = props2.originalStart;
308
+ previousSelection.end = props2.originalEnd;
309
+ const start = props2.start;
310
+ const end = props2.end;
311
+ if (start === null || end === null) {
312
+ context().setActiveSlots([]);
313
+ return;
314
+ }
315
+ const indexes = Array.from({ length: end - start }, (_, i) => start + i);
316
+ context().setActiveSlots(indexes);
317
+ };
318
+ return <>
319
+ <Show when={defaultedProps.noScriptCSSFallback !== null && isServer}>
320
+ <noscript>
321
+ <style>{defaultedProps.noScriptCSSFallback}</style>
322
+ </noscript>
323
+ </Show>
324
+ <Dynamic
325
+ as="input"
326
+ ref={mergeRefs(setRef, defaultedProps.ref)}
327
+ onInput={onInput}
328
+ onFocus={onFocus}
329
+ onBlur={onBlur}
330
+ onMouseOver={onMouseOver}
331
+ onMouseLeave={onMouseLeave}
332
+ onKeyDown={onKeyDown}
333
+ onKeyUp={onKeyUp}
334
+ inputMode="numeric"
335
+ autocomplete={defaultedProps.autocomplete ?? "one-time-code"}
336
+ disabled={defaultedProps.disabled}
337
+ spellcheck={defaultedProps.spellcheck ?? false}
338
+ style={combineStyle(
339
+ {
340
+ display: "flex",
341
+ position: "absolute",
342
+ inset: 0,
343
+ width: context().shiftPWManagers() ? "calc(100% + 40px)" : "100%",
344
+ "clip-path": context().shiftPWManagers() ? "inset(0 40px 0 0)" : void 0,
345
+ height: "100%",
346
+ padding: 0,
347
+ color: "transparent",
348
+ background: "transparent",
349
+ "caret-color": "transparent",
350
+ border: "0 solid transparent",
351
+ outline: "0 solid transparent",
352
+ "box-shadow": "none",
353
+ "line-height": "1",
354
+ "letter-spacing": "-1em",
355
+ "font-family": "monospace",
356
+ "font-variant-numeric": "tabular-nums",
357
+ "font-size": `${context().rootHeight()}px`,
358
+ "pointer-events": "all"
359
+ },
360
+ defaultedProps.style
361
+ )}
362
+ pattern={patternRegex()?.source}
363
+ data-corvu-otp-field-input=""
364
+ {...otherProps}
365
+ />
366
+ </>;
367
+ };
368
+ var DEFAULT_NOSCRIPT_CSS_FALLBACK = `
369
+ [data-corvu-otp-field-input] {
370
+ color: black !important;
371
+ background-color: white !important;
372
+ caret-color: black !important;
373
+ letter-spacing: inherit !important;
374
+ text-align: center !important;
375
+ border: 1px solid black !important;
376
+ width: 100% !important;
377
+ font-size: inherit !important;
378
+ clip-path: none !important;
379
+ }
380
+ `;
381
+ var Input_default = OtpFieldInput;
382
+
383
+ // src/Root.tsx
384
+ import {
385
+ createEffect as createEffect3,
386
+ createMemo as createMemo2,
387
+ createSignal as createSignal2,
388
+ merge as merge2,
389
+ omit as omit2,
390
+ untrack
391
+ } from "solid-js";
392
+ import { Dynamic as Dynamic2 } from "@corvu-next/utils/dynamic";
393
+ import { combineStyle as combineStyle2 } from "@corvu-next/utils/dom";
394
+ import createControllableSignal from "@corvu-next/utils/create/controllableSignal";
395
+ import createOnce from "@corvu-next/utils/create/once";
396
+ import createSize from "@corvu-next/utils/create/size";
397
+ import { isFunction } from "@corvu-next/utils";
398
+ import { mergeRefs as mergeRefs2 } from "@corvu-next/utils/reactivity";
399
+ var OtpFieldRoot = (props) => {
400
+ const defaultedProps = merge2(
401
+ {
402
+ shiftPWManagers: true
403
+ },
404
+ props
405
+ );
406
+ const localProps = omit2(
407
+ defaultedProps,
408
+ "maxLength",
409
+ "value",
410
+ "onValueChange",
411
+ "onComplete",
412
+ "shiftPWManagers",
413
+ "contextId",
414
+ "ref",
415
+ "style",
416
+ "children"
417
+ );
418
+ const [ref, setRef] = createSignal2(null);
419
+ const [value, setValue] = createControllableSignal({
420
+ value: () => defaultedProps.value,
421
+ initialValue: "",
422
+ onChange: defaultedProps.onValueChange
423
+ });
424
+ const rootHeight = createSize({
425
+ element: ref,
426
+ dimension: "height"
427
+ });
428
+ createEffect3((prev) => {
429
+ const value_ = value();
430
+ if (value_.length !== defaultedProps.maxLength) return value_;
431
+ defaultedProps.onComplete?.(value_);
432
+ return value_;
433
+ });
434
+ const [isFocused, setIsFocused] = createSignal2(false);
435
+ const [isHovered, setIsHovered] = createSignal2(false);
436
+ const [isInserting, setIsInserting] = createSignal2(false);
437
+ const [activeSlots, setActiveSlots] = createSignal2([]);
438
+ const childrenProps = {
439
+ get value() {
440
+ return value();
441
+ },
442
+ get isFocused() {
443
+ return isFocused();
444
+ },
445
+ get isHovered() {
446
+ return isHovered();
447
+ },
448
+ get isInserting() {
449
+ return isInserting();
450
+ },
451
+ get maxLength() {
452
+ return defaultedProps.maxLength;
453
+ },
454
+ get activeSlots() {
455
+ return activeSlots();
456
+ },
457
+ get shiftPWManagers() {
458
+ return defaultedProps.shiftPWManagers;
459
+ }
460
+ };
461
+ const memoizedChildren = createOnce(() => defaultedProps.children);
462
+ const resolveChildren = () => {
463
+ const children = memoizedChildren()();
464
+ if (isFunction(children)) {
465
+ return children(childrenProps);
466
+ }
467
+ return children;
468
+ };
469
+ const memoizedOtpFieldRoot = createMemo2(() => {
470
+ const OtpFieldContext2 = createOtpFieldContext(defaultedProps.contextId);
471
+ const InternalOtpFieldContext2 = createInternalOtpFieldContext(
472
+ defaultedProps.contextId
473
+ );
474
+ return <OtpFieldContext2
475
+ value={{
476
+ value,
477
+ isFocused,
478
+ isHovered,
479
+ isInserting,
480
+ maxLength: () => defaultedProps.maxLength,
481
+ activeSlots,
482
+ shiftPWManagers: () => defaultedProps.shiftPWManagers
483
+ }}
484
+ >
485
+ <InternalOtpFieldContext2
486
+ value={{
487
+ value,
488
+ isFocused,
489
+ isHovered,
490
+ isInserting,
491
+ maxLength: () => defaultedProps.maxLength,
492
+ activeSlots,
493
+ shiftPWManagers: () => defaultedProps.shiftPWManagers,
494
+ rootHeight,
495
+ setValue,
496
+ setIsFocused,
497
+ setIsHovered,
498
+ setIsInserting,
499
+ setActiveSlots
500
+ }}
501
+ >
502
+ <Dynamic2
503
+ as="div"
504
+ ref={mergeRefs2(setRef, defaultedProps.ref)}
505
+ style={combineStyle2(
506
+ {
507
+ position: "relative",
508
+ "user-select": "none",
509
+ "-webkit-user-select": "none",
510
+ "pointer-events": "none"
511
+ },
512
+ defaultedProps.style
513
+ )}
514
+ data-corvu-otp-field-root=""
515
+ {...localProps}
516
+ >
517
+ {untrack(() => resolveChildren())}
518
+ </Dynamic2>
519
+ </InternalOtpFieldContext2>
520
+ </OtpFieldContext2>;
521
+ });
522
+ return memoizedOtpFieldRoot;
523
+ };
524
+ var Root_default = OtpFieldRoot;
525
+
526
+ // src/index.ts
527
+ var OtpField = Object.assign(Root_default, {
528
+ Input: Input_default,
529
+ useContext: useOtpFieldContext
530
+ });
531
+ var index_default = OtpField;
532
+ export {
533
+ Input_default as Input,
534
+ Root_default as Root,
535
+ index_default as default,
536
+ useOtpFieldContext as useContext
537
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@corvu-next/otp-field",
3
+ "version": "0.1.0",
4
+ "description": "SolidJS 2 OTP input field component.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Jasmin Noetzli (upstream), @corvu-next contributors (fork)"
8
+ },
9
+ "type": "module",
10
+ "sideEffects": false,
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "solid": "./dist/index.jsx",
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "typesVersions": {
24
+ "*": {
25
+ "*": [
26
+ "./dist/index.d.ts"
27
+ ]
28
+ }
29
+ },
30
+ "dependencies": {
31
+ "@corvu-next/utils": "~0.1.0"
32
+ },
33
+ "peerDependencies": {
34
+ "solid-js": "2.0.0-beta.15",
35
+ "@solidjs/web": "2.0.0-beta.15"
36
+ },
37
+ "devDependencies": {
38
+ "@solidjs/web": "2.0.0-beta.15",
39
+ "esbuild-plugin-solid": "^0.6.0",
40
+ "solid-js": "2.0.0-beta.15",
41
+ "tsup": "^8.5.0",
42
+ "typescript": "^5.9.2"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "clean": "rm -rf .turbo dist node_modules",
47
+ "lint": "tsc --noEmit"
48
+ }
49
+ }