PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.6.1
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.6.1
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / team-updraft / lib-onboarding-wizard / Wizard / Onboarding / src / components / Fields / NumberInputWithControls.tsx

NumberInputWithControls.tsx in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.6.1, at vendor/team-updraft/lib-onboarding-wizard/Wizard/Onboarding/src/components/Fields/NumberInputWithControls.tsx

140 lines 4.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 memo, useState, useEffect,
3 type ChangeEvent,
4 type KeyboardEvent,
5 type ClipboardEvent
6 } from 'react';
7 import FieldWrapper from './FieldWrapper';
8 import TextInput from './TextInput';
9 import Icon from '../../utils/Icon';
10 import { __ } from '@wordpress/i18n';
11
12 interface NumberInputWithControlsProps {
13 field: {
14 id: string;
15 label?: string;
16 default?: number;
17 min?: number;
18 max?: number;
19 is_lock?: boolean;
20 tooltip?: any;
21 [key: string]: any;
22 };
23 value: number;
24 onChange: (value: number) => void;
25 }
26
27 const NumberInputWithControls = ({ field, value, onChange }: NumberInputWithControlsProps) => {
28 const min = field.min ?? 0;
29 const max = field.max ?? Infinity;
30 const disabled = field.is_lock === true;
31
32 const [internalValue, setInternalValue] = useState<number>(value);
33
34 useEffect(() => {
35 setInternalValue(value);
36 }, [value]);
37
38 const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
39 const sanitizedValue = e.target.value.replace(/[^0-9]/g, '');
40 const newValue = sanitizedValue === '' ? min : parseInt(sanitizedValue, 10);
41
42 const clampedValue = Math.max(min, Math.min(max, newValue));
43 setInternalValue(clampedValue);
44 onChange(clampedValue);
45 };
46
47 const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
48 if (
49 ['Backspace', 'Delete', 'Tab', 'Escape', 'Enter', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key) ||
50 ((e.ctrlKey || e.metaKey) && ['a', 'c', 'v', 'x'].includes(e.key.toLowerCase()))
51 ) {
52 return;
53 }
54 if (!/^\d$/.test(e.key)) {
55 e.preventDefault();
56 }
57 };
58
59 const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
60 e.preventDefault();
61 if (disabled) return;
62
63 const pasteData = e.clipboardData.getData('text');
64 const sanitizedPasteData = pasteData.replace(/[^0-9]/g, '');
65
66 const newValueString = sanitizedPasteData;
67
68 const newValue = newValueString === '' ? min : parseInt(newValueString, 10);
69 const clampedValue = Math.max(min, Math.min(max, newValue));
70
71 setInternalValue(clampedValue);
72 onChange(clampedValue);
73
74 // After pasting, set the cursor to the end of the new value.
75 setTimeout(() => {
76 const currentTarget = e.target as HTMLInputElement;
77 currentTarget.value = String(clampedValue);
78 currentTarget.setSelectionRange(currentTarget.value.length, currentTarget.value.length);
79 }, 0);
80 };
81
82 const handleIncrement = () => {
83 if (!disabled && internalValue < max) {
84 const newValue = internalValue + 1;
85 setInternalValue(newValue);
86 onChange(newValue);
87 }
88 };
89
90 const handleDecrement = () => {
91 if (!disabled && internalValue > min) {
92 const newValue = internalValue - 1;
93 setInternalValue(newValue);
94 onChange(newValue);
95 }
96 };
97
98 return (
99 <FieldWrapper
100 inputId={field.id}
101 label={field.label}
102 tooltip={field.tooltip}
103 >
104 <div className="flex w-full items-center border border-gray-400 rounded-md overflow-hidden">
105 <TextInput
106 id={field.id}
107 type="number"
108 value={internalValue}
109 onChange={handleInputChange}
110 onKeyDown={handleKeyDown}
111 onPaste={handlePaste}
112 min={min}
113 max={max}
114 disabled={disabled}
115 className="text-center flex-1 border-none focus:ring-0 focus:border-none rounded-none bg-white"
116 />
117 <div className="flex h-full divide-x divide-gray-400">
118 <button
119 type="button"
120 onClick={handleDecrement}
121 disabled={disabled || internalValue <= min}
122 className="p-2 bg-gray-100 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
123 >
124 <Icon name="minus" size={16} color="gray-700" />
125 </button>
126 <button
127 type="button"
128 onClick={handleIncrement}
129 disabled={disabled || internalValue >= max}
130 className="p-2 bg-gray-100 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
131 >
132 <Icon name="plus" size={16} color="gray-700" />
133 </button>
134 </div>
135 </div>
136 </FieldWrapper>
137 );
138 };
139
140 export default memo(NumberInputWithControls);